diff --git a/.claude/skills/accessibility/SKILL.md b/.claude/skills/accessibility/SKILL.md
index 6b30fa13a..6b4a9500c 100644
--- a/.claude/skills/accessibility/SKILL.md
+++ b/.claude/skills/accessibility/SKILL.md
@@ -12,12 +12,10 @@ This project builds on **shadcn/ui** primitives which provide strong built-in a1
All interactive elements without visible text must have an `aria-label`:
```typescript
-// Icon buttons
-// Folder toggles
```
@@ -45,11 +43,15 @@ Use the shadcn/ui `Label` component for proper form associations.
- **Escape**: Close dialogs, cancel editing, deselect
- **Tab**: Move focus between interactive elements
+The `Input` component has built-in `onEnter` and `onEscape` props:
+
```typescript
-// The Input component has built-in onEnter and onEscape props
+```
+
+Everything else handles the keys itself:
-// For non-Input elements, handle manually
+```typescript
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
@@ -78,13 +80,11 @@ The dialog component already handles this — use `preventKeyboardPropagation` p
Use `sr-only` class for visually hidden but screen-reader-accessible text:
```typescript
-// Close buttons with only an icon
Close
-// Command palette title
{title}
{description}
@@ -95,19 +95,10 @@ Use `sr-only` class for visually hidden but screen-reader-accessible text:
Use proper semantic elements and ARIA roles:
-```typescript
-// Navigation
-
-
-// Current page in breadcrumb
-{children}
-
-// Decorative separators
-
-
-// Alerts
-
-```
+- Navigation — `
`
+- The current page in a breadcrumb — ``
+- Decorative separators — ``
+- Alerts — ``
## Focus Styling
diff --git a/.claude/skills/analytics-tracking/SKILL.md b/.claude/skills/analytics-tracking/SKILL.md
index 712d42185..809595961 100644
--- a/.claude/skills/analytics-tracking/SKILL.md
+++ b/.claude/skills/analytics-tracking/SKILL.md
@@ -39,8 +39,10 @@ Pass a second argument for event-specific properties. The metadata is serialized
When a child component renders the interactive element but the parent owns the tracking context, prefer prop drilling or forwarding rest props so `data-tracking-id` reaches the DOM element. This is intentional — it keeps tracking declarative and avoids manual `track()` calls scattered across the codebase.
+The parent passes the tracking attributes, and `ActionButton` forwards its rest props down the
+`
` → `` → DOM chain:
+
```tsx
-// Parent passes tracking attributes
;
-// ActionButton forwards rest props to the underlying → → DOM
export const ActionButton = ({
tooltip,
onClick,
@@ -102,13 +103,11 @@ import { useAnalytics } from "@/providers/AnalyticsProvider";
const { track } = useAnalytics();
-// Outcome — fires after success, not on click
const handleSave = async (name: string) => {
await savePipeline(name);
track("pipeline_editor.pipeline_actions.save_pipeline_as_completed");
};
-// Impression — fires when dialog opens
useEffect(() => {
if (open) {
track("component_editor.save.already_exists_impression");
@@ -116,6 +115,9 @@ useEffect(() => {
}, [open]);
```
+The first is an outcome event — it fires after the save succeeds, not on click. The second is an
+impression event, firing when the dialog opens.
+
## Event detail shape
The dispatched `CustomEvent` carries the following `detail` fields:
diff --git a/.claude/skills/project-conventions/SKILL.md b/.claude/skills/project-conventions/SKILL.md
index 45ad3319d..8a81c2f9a 100644
--- a/.claude/skills/project-conventions/SKILL.md
+++ b/.claude/skills/project-conventions/SKILL.md
@@ -1,6 +1,6 @@
---
name: project-conventions
-description: Project conventions for Tangle-UI including file structure, imports, code quality, and general rules. Use when writing new code, creating files, or organizing imports.
+description: Project conventions for Tangle-UI including file structure, imports, comments, code quality, and general rules. Use when writing new code, editing existing code, adding or reviewing comments and JSDoc, creating files, or organizing imports.
---
# Project Conventions
@@ -28,7 +28,6 @@ React + TypeScript application for building and running ML pipelines using drag
- Use Prettier for formatting
- Write tests using Vitest for unit tests, Playwright for E2E
- Use descriptive variable and function names
-- Add JSDoc comments for complex functions
- Prefer early returns to reduce nesting
## Comments & Documentation
diff --git a/.claude/skills/react-patterns/SKILL.md b/.claude/skills/react-patterns/SKILL.md
index 4495ad243..f8f322d0d 100644
--- a/.claude/skills/react-patterns/SKILL.md
+++ b/.claude/skills/react-patterns/SKILL.md
@@ -18,13 +18,13 @@ description: React and React Compiler patterns for this project. Use when writin
```typescript
// ComponentName/ComponentName.tsx — import directly, no index.ts barrel
interface ComponentNameProps {
- // props
+ ...
}
export const ComponentName = ({ }: ComponentNameProps) => {
- // component logic
+ ...
return (
- // JSX
+ ...
);
};
```
@@ -42,7 +42,7 @@ export const ComponentName = ({ }: ComponentNameProps) => {
const Context = createContext(null);
export const Provider = ({ children }: { children: ReactNode }) => {
- // provider logic
+ ...
return {children} ;
};
diff --git a/.claude/skills/tangle-domain/SKILL.md b/.claude/skills/tangle-domain/SKILL.md
index 34db3ca1c..b02b6d50b 100644
--- a/.claude/skills/tangle-domain/SKILL.md
+++ b/.claude/skills/tangle-domain/SKILL.md
@@ -142,7 +142,6 @@ Arbitrary key-value metadata (`Record`) on components, tasks, i
Sensitive values (API keys, credentials) stored securely in the backend and referenced by name in task arguments via `SecretArgument`. Resolved at runtime — never embedded in pipeline definitions or exports.
```typescript
-// SecretArgument structure
{
secret: {
name: "my-api-key";
diff --git a/.claude/skills/tanstack-query/SKILL.md b/.claude/skills/tanstack-query/SKILL.md
index 6a8ac309a..1f478643e 100644
--- a/.claude/skills/tanstack-query/SKILL.md
+++ b/.claude/skills/tanstack-query/SKILL.md
@@ -14,13 +14,15 @@ This project uses TanStack Query v5 for all server state management.
Use **hierarchical array-based keys**. For domains with multiple related queries, use a query key factory:
```typescript
-// Query key factory pattern (preferred for grouped queries)
export const SecretsQueryKeys = {
All: () => ["secrets"] as const,
Id: (id: string) => ["secrets", id] as const,
} as const;
+```
+
+Standalone queries just use a simple key:
-// Simple keys for standalone queries
+```typescript
queryKey: ["pipeline-run", rootExecutionId];
queryKey: ["execution-details", rootExecutionId];
queryKey: ["component", "hydrate", componentQueryKey];
diff --git a/.claude/skills/tanstack-router/SKILL.md b/.claude/skills/tanstack-router/SKILL.md
index c59b2bb63..314009f16 100644
--- a/.claude/skills/tanstack-router/SKILL.md
+++ b/.claude/skills/tanstack-router/SKILL.md
@@ -24,7 +24,6 @@ const indexRoute = createRoute({
component: Editor,
});
-// Assemble tree
const appRouteTree = mainLayout.addChildren([
indexRoute,
quickStartRoute,
diff --git a/.claude/skills/vitest-testing/SKILL.md b/.claude/skills/vitest-testing/SKILL.md
index 3a9c1f30d..9bde8d18b 100644
--- a/.claude/skills/vitest-testing/SKILL.md
+++ b/.claude/skills/vitest-testing/SKILL.md
@@ -115,14 +115,12 @@ vi.mock("@/utils/localforage", () => ({
saveComponent: vi.fn(),
}));
-// React component mock
vi.mock("@monaco-editor/react", () => ({
default: ({ defaultValue }: { defaultValue: string }) => (
{defaultValue}
),
}));
-// Provider/context mock
vi.mock("@/providers/ComponentSpecProvider", () => ({
useComponentSpec: () => ({
componentSpec: mockSpec,
@@ -135,7 +133,7 @@ vi.mock("@/providers/ComponentSpecProvider", () => ({
```typescript
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
-// ... test code
+// ...
expect(consoleSpy).toHaveBeenCalledWith("Error:", expect.any(Error));
```
@@ -184,25 +182,34 @@ beforeEach(() => {
});
afterEach(() => {
- cleanup(); // Testing Library cleanup
- queryClient.clear(); // Clear query cache if using QueryClient
+ cleanup();
+ queryClient.clear();
vi.restoreAllMocks();
});
```
+Only call `queryClient.clear()` when the suite uses a `QueryClient`.
+
## Assertion Patterns
+DOM:
+
```typescript
-// DOM assertions
expect(screen.getByTestId("submit")).toBeInTheDocument();
expect(screen.queryByTestId("hidden")).not.toBeInTheDocument();
+```
+
+Mocks:
-// Mock assertions
+```typescript
expect(mockFn).toHaveBeenCalledWith(url);
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).not.toHaveBeenCalled();
+```
-// Partial matching
+Partial matching:
+
+```typescript
expect(mockSave).toHaveBeenCalledWith({
id: expect.stringMatching(/^component-\w+$/),
createdAt: expect.any(Number),
@@ -223,12 +230,10 @@ expect(mockCallback).toHaveBeenCalled();
## Async Testing
```typescript
-// Wait for state updates
await waitFor(() => {
expect(screen.getByTestId("content")).toBeInTheDocument();
});
-// Assert on promises
await expect(promise).resolves.toBe(expectedValue);
```
diff --git a/.cursorrules b/.cursorrules
index b49cf0b3a..fdefc974d 100644
--- a/.cursorrules
+++ b/.cursorrules
@@ -95,7 +95,6 @@ This is a React + TypeScript application for building and running Machine Learni
- Use Prettier for formatting
- Write tests using Vitest for unit tests, Playwright for E2E
- Use descriptive variable and function names
-- Add JSDoc comments for complex functions
- Prefer early returns to reduce nesting
### API & Data
@@ -416,11 +415,9 @@ export const useContext = () => {
## Comments & Documentation
-- Use JSDoc for public APIs
-- Add comments for complex business logic
-- **Explain "why" not "what" in comments**
-- **Keep comments up to date with code changes**
-- Avoid writing redundant comments for functions and variables that are self-explanatory
+The comments policy lives in `CLAUDE.md` (root) — that is the single source of truth. In short:
+code must be self-explanatory, default to no comment, comment only a non-obvious _why_, never
+comment interface fields or props (rename them instead).
## Specific Project Patterns
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 000000000..7b64233d8
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,26 @@
+# Tangle-UI
+
+## Comments
+
+Code must be self-explanatory. Default to **no comment**.
+
+- A comment is justified only when it states a non-obvious **why** the code and its names cannot: a
+ workaround, a race condition, an external-system quirk, a subtle ordering constraint.
+- Never narrate **what** the code does. No step-by-step narration (`// fetch the data`), no
+ section-divider banners (`// ─── Helpers ───`), no restating a name.
+- Never comment `interface` fields, `type` members, component `Props`, or config-object fields —
+ leading or trailing, `//` or `/** */`. A field that needs a comment to explain what it is has a
+ **naming problem**: rename it. (Only exception: the prop contract of a shared design-system
+ primitive in `src/components/ui/`.)
+- Prefer renaming, extracting a well-named function, or a named constant over adding a comment.
+- JSDoc only for genuinely complex functions and shared public APIs — never as a substitute for a
+ good name.
+- A stale comment is worse than none: update comments with the code, and delete ones you invalidate.
+
+Removing a comment that violates the above is always in scope for a change you are already making.
+
+## Everything else
+
+Project conventions live in `.claude/skills/` — start with `project-conventions`, plus
+`typescript-standards`, `react-patterns`, `ui-primitives`, `tangle-domain`, `tanstack-query`,
+`tanstack-router`, `vitest-testing`, `e2e-testing`, `accessibility`, and `open-source`.
diff --git a/docs/generic-overlay-guide.md b/docs/generic-overlay-guide.md
index 137bba182..dbcd3d978 100644
--- a/docs/generic-overlay-guide.md
+++ b/docs/generic-overlay-guide.md
@@ -237,7 +237,6 @@ type ProfileLinkReplacements = {
userId: string;
};
-// userProfile comes from an API hook (e.g., useFetchUserProfile)
export function resolveGreetingReplacements(
metadata: Record,
userProfile: { name: string; retriesUsed: number },
@@ -300,7 +299,6 @@ export const StatusOverlaySection = ({
profileLink: resolveProfileLinkReplacements(userId),
};
- // Pass null for displayFor when no conditional rendering is needed
const hydrated = filterAndHydrateSchema(schema, allReplacements);
return ;
};
diff --git a/docs/playwright-test-helpers.md b/docs/playwright-test-helpers.md
index 226dc0014..67f3803cd 100644
--- a/docs/playwright-test-helpers.md
+++ b/docs/playwright-test-helpers.md
@@ -13,7 +13,6 @@ The Pipeline Studio app provides a comprehensive set of helper functions in `tes
Creates a new pipeline by navigating to home and clicking the new pipeline button.
```typescript
-// Usage
await createNewPipeline(page);
```
@@ -27,14 +26,13 @@ await createNewPipeline(page);
#### Canvas Actions
+- `clickOnCanvas(page, x, y)` - click at coordinates (defaults `x=400`, `y=300`)
+- `panCanvas(page, deltaX, deltaY)` - pan the canvas (defaults `deltaX=50`, `deltaY=50`)
+- `zoomIn(page)`, `zoomOut(page)`, `fitToView(page)` - zoom controls
+
```typescript
-// Click on canvas at specific coordinates (defaults: x=400, y=300)
await clickOnCanvas(page, 400, 300);
-
-// Pan the canvas (defaults: deltaX=50, deltaY=50)
await panCanvas(page, deltaX, deltaY);
-
-// Zoom controls
await zoomIn(page);
await zoomOut(page);
await fitToView(page);
@@ -44,19 +42,18 @@ await fitToView(page);
#### Working with Component Folders
+`dropComponentFromLibraryOnCanvas` does the whole open-locate-drag workflow in one call; its fourth
+argument is optional.
+
```typescript
-// Open a component library folder
const folder = await openComponentLibFolder(page, "Quick start");
-
-// Locate component within folder
const component = locateComponentInFolder(folder, "Chicago Taxi Trips dataset");
-// Complete workflow: drag component to canvas
const node = await dropComponentFromLibraryOnCanvas(
page,
- "Quick start", // folder name
- "Chicago Taxi Trips dataset", // component name
- { targetPosition: { x: 400, y: 300 } }, // optional drag options
+ "Quick start",
+ "Chicago Taxi Trips dataset",
+ { targetPosition: { x: 400, y: 300 } },
);
```
@@ -65,10 +62,8 @@ const node = await dropComponentFromLibraryOnCanvas(
#### Node Selection and Manipulation
```typescript
-// Locate node by name
const node = locateNodeByName(page, "Chicago Taxi Trips dataset");
-// Click and verify selection
await node.click();
await expect(node).toHaveClass(/\bselected\b/);
```
@@ -78,14 +73,10 @@ await expect(node).toHaveClass(/\bselected\b/);
#### Working with Side Panels
```typescript
-// Wait for specific context panel to appear
await waitForContextPanel(page, "pipeline-details");
await waitForContextPanel(page, "task-overview");
-// Locate context panel container
const container = locateContextPanelContainer(page);
-
-// Locate specific context panel
const panel = locateContextPanel(page, "pipeline-details");
```
@@ -97,19 +88,14 @@ const panel = locateContextPanel(page, "pipeline-details");
test("should load pipeline editor and allow basic interaction", async ({
page,
}) => {
- // Setup
await createNewPipeline(page);
- // Verify canvas components
await expect(locateFlowCanvas(page)).toBeVisible();
await expect(locateFlowViewport(page)).toBeVisible();
-
- // Check React Flow UI components
await expect(page.locator(".react-flow__minimap")).toBeVisible();
await expect(page.locator(".react-flow__background")).toBeVisible();
await expect(page.locator(".react-flow__controls")).toBeVisible();
- // Test interactions
await clickOnCanvas(page, 400, 300);
await panCanvas(page, 50, 50);
await zoomIn(page);
@@ -123,20 +109,15 @@ test("should load pipeline editor and allow basic interaction", async ({
test("should place and select nodes", async ({ page }) => {
await createNewPipeline(page);
- // Add component from library
const node = await dropComponentFromLibraryOnCanvas(
page,
"Quick start",
"Chicago Taxi Trips dataset",
);
- // Verify placement
await expect(node).toBeVisible();
-
- // Verify the pipeline details panel is visible initially
await waitForContextPanel(page, "pipeline-details");
- // Test selection
await node.click();
await expect(node).toHaveClass(/\bselected\b/);
await waitForContextPanel(page, "task-overview");
@@ -149,18 +130,15 @@ test("should place and select nodes", async ({ page }) => {
test("should connect two nodes", async ({ page }) => {
await createNewPipeline(page);
- // Place first node
const nodeA = await dropComponentFromLibraryOnCanvas(
page,
"Quick start",
"Chicago Taxi Trips dataset",
);
- // Position for second node
const nodeABox = await nodeA.boundingBox();
await panCanvas(page, -nodeABox!.width, 0);
- // Place second node
const nodeB = await dropComponentFromLibraryOnCanvas(
page,
"Quick start",
@@ -168,40 +146,34 @@ test("should connect two nodes", async ({ page }) => {
{ targetPosition: { x: nodeABox!.width * 1.5, y: nodeABox!.y } },
);
- // Verify both nodes are visible
await expect(nodeA).toBeVisible();
await expect(nodeB).toBeVisible();
- // Locate connection points
const outputPin = nodeA.locator('[data-handleid="output_Table"]');
const inputPin = nodeB.locator('[data-handleid="input_training_data"]');
- // Ensure both connectors are within viewport
+ // A drag only registers when both pins are inside the viewport.
await fitToView(page);
- // Verify pins are visible and input shows required state (red)
await expect(outputPin).toBeInViewport();
await expect(inputPin).toBeInViewport();
+ // bg-red-700 marks a required input that is not yet connected.
await expect(inputPin).toHaveClass(/\bbg-red-700\b/);
- // Connect nodes via drag and drop
await outputPin.hover();
await page.mouse.down();
await inputPin.hover();
await page.mouse.up();
- // Verify connection state changes
await expect(inputPin).not.toHaveClass(/\bbg-red-700\b/);
await expect(inputPin).toHaveClass(/\bbg-gray-500\b/);
- // Verify edge is created
const edgesContainer = page.locator(".react-flow__edges");
const edge = edgesContainer.locator(
'[data-testid="rf__edge-Chicago Taxi Trips dataset_Table-Train XGBoost model on CSV_training_data"]',
);
await expect(edge).toBeVisible();
- // Verify input handle shows connection data
const inputHandle = nodeB.locator(
'[data-testid="input-handle-training_data"]',
);
@@ -247,17 +219,13 @@ test("should connect two nodes", async ({ page }) => {
### Handle and Connection Selectors
+Edge test ids follow the format `source_output-target_input`.
+
```typescript
-// Output handles
const outputPin = node.locator('[data-handleid="output_Table"]');
-
-// Input handles
const inputPin = node.locator('[data-handleid="input_training_data"]');
-
-// Input handle containers
const inputHandle = node.locator('[data-testid="input-handle-training_data"]');
-// Edge connections (format: source_output-target_input)
const edge = page.locator(
'[data-testid="rf__edge-Chicago Taxi Trips dataset_Table-Train XGBoost model on CSV_training_data"]',
);
@@ -278,14 +246,12 @@ const edge = page.locator(
### 3. Node Positioning
+Read the existing node's box, pan to make room, then position the new node relative to it.
+
```typescript
-// Get node dimensions for relative positioning
const nodeBox = await node.boundingBox();
-
-// Pan canvas to make room for additional nodes
await panCanvas(page, -nodeBox!.width, 0);
-// Position new nodes relative to existing ones
const newNode = await dropComponentFromLibraryOnCanvas(
page,
"Quick start",
@@ -296,11 +262,13 @@ const newNode = await dropComponentFromLibraryOnCanvas(
### 4. Connection Testing
+Always assert the connection state both before and after connecting. The state lives in the pin's
+class: `bg-red-700` means required-but-unconnected, `bg-gray-500` means connected.
+
```typescript
-// Always verify connection state before and after
-await expect(inputPin).toHaveClass(/\bbg-red-700\b/); // Required state
+await expect(inputPin).toHaveClass(/\bbg-red-700\b/);
// ... perform connection ...
-await expect(inputPin).toHaveClass(/\bbg-gray-500\b/); // Connected state
+await expect(inputPin).toHaveClass(/\bbg-gray-500\b/);
```
### 5. Async Operations
@@ -317,11 +285,12 @@ await expect(inputPin).toHaveClass(/\bbg-gray-500\b/); // Connected state
### 7. Debugging
+`page.pause()` opens the inspector for interactive debugging, and logging a bounding box helps with
+positioning problems.
+
```typescript
-// Use page.pause() for interactive debugging
await page.pause();
-// Log bounding boxes for positioning issues
const nodeBox = await node.boundingBox();
console.log("Node position:", nodeBox);
```
@@ -370,16 +339,23 @@ pnpm run test:e2e:headed
### Complete Function Signatures
+#### Setup
+
```typescript
-// Setup
export async function createNewPipeline(page: Page): Promise;
+```
-// Canvas locators
+#### Canvas locators
+
+```typescript
export function locateFlowCanvas(page: Page): Locator;
export function locateFlowViewport(page: Page): Locator;
export function locateFlowPane(page: Page): Locator;
+```
-// Canvas interactions
+#### Canvas interactions
+
+```typescript
export async function clickOnCanvas(
page: Page,
x: number = 400,
@@ -393,8 +369,11 @@ export async function panCanvas(
export async function zoomIn(page: Page): Promise;
export async function zoomOut(page: Page): Promise;
export async function fitToView(page: Page): Promise;
+```
+
+#### Component library
-// Component library
+```typescript
export async function openComponentLibFolder(
page: Page,
folderName: string,
@@ -408,19 +387,28 @@ export async function dragComponentToCanvas(
component: Locator,
dragOptions: DragOptions = {},
): Promise;
+```
-// Nodes
+#### Nodes
+
+```typescript
export function locateNodeByName(page: Page, nodeName: string): Locator;
+```
+
+#### Context panels
-// Context panels
+```typescript
export function locateContextPanelContainer(page: Page): Locator;
export function locateContextPanel(page: Page, panelName: string): Locator;
export async function waitForContextPanel(
page: Page,
panelName: string,
): Promise;
+```
-// Complete workflows
+#### Complete workflows
+
+```typescript
export async function dropComponentFromLibraryOnCanvas(
page: Page,
folderName: string,
diff --git a/docs/react-best-practices.md b/docs/react-best-practices.md
index 30eef1b9e..efec70600 100644
--- a/docs/react-best-practices.md
+++ b/docs/react-best-practices.md
@@ -13,11 +13,10 @@ Initializing non-primitives during render and passing these values as props or d
This could impact performance and/or introduce bugs, or even make it harder to implement some features.
```tsx
-// ❌ The new callback is considered unstable.
+// ❌ The new callback is unstable, so the effect triggers every render.
const eventHandler = () => sendEvent({ shopId });
useEffect(() => {
- // Triggers every render
eventHandler();
}, [eventHandler, shopId]);
```
@@ -129,7 +128,6 @@ export const SubmitButton = ({ onClick, disabled }) => (
// src/components/Editor/PipelineEditor.tsx
import { SubmitButton } from "../shared/SubmitButton";
-// Only used here...
```
```tsx
@@ -142,7 +140,6 @@ const SubmitButton = ({ onClick, disabled }) => (
);
export const PipelineEditor = () => {
- // Use SubmitButton directly in the same file
return ;
};
```
@@ -151,7 +148,7 @@ export const PipelineEditor = () => {
// ✅ GOOD: Move to sibling file when used by multiple components in the same feature
// src/components/Editor/components/SubmitButton.tsx
export const SubmitButton = ({ onClick, disabled }) => {
- // Component implementation
+ // ...
};
// src/components/Editor/PipelineEditor.tsx
@@ -167,19 +164,18 @@ import { SubmitButton } from "./components/SubmitButton";
// ❌ BAD: Creating a generic hook in /hooks when it's feature-specific
// src/hooks/usePipelineValidation.ts
export const usePipelineValidation = () => {
- // Pipeline-specific validation logic
+ // ...
};
// src/components/Editor/PipelineEditor.tsx
import { usePipelineValidation } from "../../hooks/usePipelineValidation";
-// Only the Editor feature uses this...
```
```tsx
// ✅ GOOD: Keep feature-specific hooks with the feature
// src/components/Editor/hooks/usePipelineValidation.ts
export const usePipelineValidation = () => {
- // Pipeline-specific validation logic
+ // ...
};
// src/components/Editor/PipelineEditor.tsx
@@ -192,7 +188,6 @@ import { usePipelineValidation } from "./hooks/usePipelineValidation";
// ❌ BAD: Putting very specific logic in a general utils folder
// src/utils/formatters.ts
export const formatPipelineNodeLabel = (node) => {
- // Very specific to pipeline nodes...
return `${node.type}: ${node.name}`;
};
@@ -229,7 +224,7 @@ export const formatDate = (date: Date): string => {
// ✅ GOOD: This button is used across many unrelated features
// src/components/ui/button.tsx
export const Button = ({ variant, size, children, ...props }) => {
- // Generic button implementation
+ // ...
};
```
@@ -269,7 +264,7 @@ export function useHydrateComponentReference(component: ComponentReference) {
const { data: componentRef } = useSuspenseQuery({
queryKey: ["component", "hydrate", component.digest ?? component.url],
queryFn: () => hydrateComponentReference(component),
- staleTime: 1000 * 60 * 60 * 1, // 1 hour
+ staleTime: ONE_HOUR_MS,
retryOnMount: true,
});
@@ -295,7 +290,7 @@ export function useHydrateComponentReference(component: ComponentReference) {
const { data } = useSuspenseQuery({
queryKey: ["user", userId, "posts", { status: "published" }],
queryFn: () => fetchUserPosts(userId, { status: "published" }),
- staleTime: 1000 * 60 * 5, // 5 minutes for user-specific data
+ staleTime: FIVE_MINUTES_MS,
});
```
@@ -306,7 +301,6 @@ The `withSuspenseWrapper()` Higher-Order Component provides a consistent way to
```tsx
// ✅ GOOD: Component that fetches data using useSuspenseQuery
const ComponentDetailsDialogContent = ({ componentRef }: Props) => {
- // This will suspend the component
const hydratedComponent = useHydrateComponentReference(componentRef);
return (
@@ -316,7 +310,6 @@ const ComponentDetailsDialogContent = ({ componentRef }: Props) => {
);
};
-// Create a skeleton that matches the component's layout
const ComponentDetailsSkeleton = () => {
return (
@@ -332,7 +325,6 @@ const ComponentDetailsSkeleton = () => {
);
};
-// Export the wrapped component
export const ComponentDetailsDialog = withSuspenseWrapper(
ComponentDetailsDialogContent,
ComponentDetailsSkeleton,
@@ -394,7 +386,7 @@ import { Skeleton } from "@/components/ui/skeleton";
const ComplexSkeleton = () => {
const [animationState, setAnimationState] = useState(0);
useEffect(() => {
- // Unnecessary complexity
+ // ...
}, []);
return ...
;
@@ -414,16 +406,15 @@ const SimpleSkeleton = () => (
The `withSuspenseWrapper()` HOC includes error boundary handling with retry capabilities:
```tsx
-// The wrapper automatically provides error UI with retry
export const MyComponent = withSuspenseWrapper(
MyComponentContent,
MyComponentSkeleton,
);
-
-// Users will see a friendly error message with a "Try Again" button
-// that will reset the error boundary and retry the failed queries
```
+On failure users see a friendly error message with a "Try Again" button, which resets the error
+boundary and retries the failed queries.
+
### Composition Patterns
#### Pattern 1: Page-Level Suspense
@@ -485,7 +476,6 @@ const EditableComponent = () => {
const mutation = useMutation({
mutationFn: updateComponent,
onSuccess: () => {
- // Invalidate to trigger suspense refetch
queryClient.invalidateQueries({ queryKey: ["component", id] });
},
});
@@ -503,7 +493,7 @@ const EditableComponent = () => {
try {
const data = useSuspenseQuery(...);
} catch (promise) {
- // Don't do this!
+ // ...
}
// ✅ GOOD: Let Suspense boundaries handle it
@@ -516,9 +506,10 @@ const data = useSuspenseQuery(...);
// ❌ BAD: Mixing suspense with manual loading states
const Component = () => {
const [isLoading, setIsLoading] = useState(true);
- const { data } = useSuspenseQuery(...); // This suspends!
+ const { data } = useSuspenseQuery(...);
- if (isLoading) return ; // Never reached
+ // Never reached: the query suspends before this branch can render.
+ if (isLoading) return ;
return {data}
;
};
@@ -579,11 +570,9 @@ const renderWithProviders = (component: React.ReactElement) => {
);
};
-// In your test
test("loads and displays data", async () => {
renderWithProviders( );
- // Wait for suspense to resolve
await waitFor(() => {
expect(screen.getByText("Expected Content")).toBeInTheDocument();
});
@@ -937,10 +926,7 @@ When testing components that use UI primitives:
test("displays error message", () => {
render( );
- // Don't test for specific classes
// ❌ expect(screen.getByRole("alert")).toHaveClass("bg-red-50");
-
- // Test for content and semantics
// ✅
expect(screen.getByRole("alert")).toHaveTextContent("Error occurred");
});
diff --git a/docs/remote-troubleshoot-action.md b/docs/remote-troubleshoot-action.md
index d95c649d2..1fcfd39f4 100644
--- a/docs/remote-troubleshoot-action.md
+++ b/docs/remote-troubleshoot-action.md
@@ -38,32 +38,26 @@ Leaving the global unset (the default) disables the feature entirely — the but
```typescript
interface RemoteTroubleshootActionConfig {
- /** URL the payload will be POSTed to. */
endpointUrl: string;
-
- /** Label shown on the button and used as the default modal title. */
buttonText: string;
-
- /** Optional modal title (defaults to buttonText). */
modalTitle?: string;
-
- /** Optional modal description shown above the comments textarea. */
modalDescription?: string;
-
- /** Optional heading shown in the success state (defaults to "Request submitted"). */
successTitle?: string;
-
- /** Optional body shown in the success state, and persisted in the task panel after submission. */
successMessage?: string;
-
- /**
- * Optional source tag included in the payload (defaults to "tangle-ui").
- * Use this to distinguish requests from different deployments.
- */
source?: string;
}
```
+| Field | Required | Meaning |
+| ------------------ | -------- | -------------------------------------------------------------------------------------------------------------- |
+| `endpointUrl` | yes | URL the payload is POSTed to |
+| `buttonText` | yes | Label on the button, and the default modal title |
+| `modalTitle` | no | Modal title; defaults to `buttonText` |
+| `modalDescription` | no | Shown above the comments textarea |
+| `successTitle` | no | Heading in the success state; defaults to "Request submitted" |
+| `successMessage` | no | Body in the success state, also persisted in the task panel after submit |
+| `source` | no | Source tag in the payload; defaults to `"tangle-ui"`. Use it to tell requests from different deployments apart |
+
### Minimal example
Only `endpointUrl` and `buttonText` are required:
diff --git a/eslint-rules/no-type-member-comments.js b/eslint-rules/no-type-member-comments.js
new file mode 100644
index 000000000..ef3849bee
--- /dev/null
+++ b/eslint-rules/no-type-member-comments.js
@@ -0,0 +1,43 @@
+const MESSAGE =
+ "No block comments on interface fields, type members, or Props. A field that needs a comment to say what it is should be renamed instead. If you must record a non-obvious constraint, use a // line comment. See CLAUDE.md.";
+
+const noTypeMemberComments = {
+ meta: {
+ type: "suggestion",
+ docs: {
+ description:
+ "Disallow block comments (/* */ and /** */) inside interface and type-literal bodies.",
+ },
+ schema: [],
+ messages: { noBlockComment: MESSAGE },
+ },
+ create(context) {
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
+ const typeBodies = [];
+
+ return {
+ "TSInterfaceBody, TSTypeLiteral"(node) {
+ typeBodies.push(node.range);
+ },
+ "Program:exit"() {
+ for (const comment of sourceCode.getAllComments()) {
+ if (comment.type !== "Block") continue;
+
+ const isInsideTypeBody = typeBodies.some(
+ ([start, end]) =>
+ start <= comment.range[0] && comment.range[1] <= end,
+ );
+ if (!isInsideTypeBody) continue;
+
+ context.report({ loc: comment.loc, messageId: "noBlockComment" });
+ }
+ },
+ };
+ },
+};
+
+export default {
+ rules: {
+ "no-type-member-comments": noTypeMemberComments,
+ },
+};
diff --git a/eslint.config.js b/eslint.config.js
index cbc32b6a7..6d88435a3 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -7,6 +7,7 @@ import simpleImportSort from "eslint-plugin-simple-import-sort";
import globals from "globals";
import tseslint from "typescript-eslint";
+import localRules from "./eslint-rules/no-type-member-comments.js";
import { REACT_COMPILER_ENABLED_GLOBS } from "./react-compiler.config.js";
const baseRestrictedImportPaths = [
@@ -63,6 +64,15 @@ export default [
"react-compiler": reactCompiler,
},
},
+ // Comments policy (CLAUDE.md): no JSDoc/block comments on type members.
+ {
+ files: ["**/*.{ts,tsx}"],
+ ignores: ["src/components/ui/**"],
+ plugins: { local: localRules },
+ rules: {
+ "local/no-type-member-comments": "warn",
+ },
+ },
// React Compiler enabled directories: warn about unnecessary useCallback/useMemo
{
files: REACT_COMPILER_ENABLED_GLOBS,
diff --git a/src/components/shared/ComponentDetail/ComponentDetail.tsx b/src/components/shared/ComponentDetail/ComponentDetail.tsx
index 8ab18f109..d7574ca16 100644
--- a/src/components/shared/ComponentDetail/ComponentDetail.tsx
+++ b/src/components/shared/ComponentDetail/ComponentDetail.tsx
@@ -166,26 +166,20 @@ const CompactIO = ({
// ─── Detail panel (suspends on hydration) ───────────────────────────────────
interface ComponentDetailProps {
- /**
- * The reference to render. Callers pass whatever they have — a hydrated ref
- * (no network needed) or a stub with `digest`+`url` (one hydration round-trip).
- * Hydration is keyed by digest/url, so cache is shared with other usages.
- */
+ // The reference to render. Callers pass whatever they have — a hydrated ref
+ // (no network needed) or a stub with `digest`+`url` (one hydration round-trip).
+ // Hydration is keyed by digest/url, so cache is shared with other usages.
reference: ComponentReference;
- /**
- * - `split` (V1 default): metadata+I/O on the left, sticky source code panel
- * on the right. Best for full-bleed detail pages.
- * - `stacked`: single column — metadata, I/O, then source code in a card with
- * a capped height. Best for narrower detail panes alongside other content.
- */
+ // - `split` (V1 default): metadata+I/O on the left, sticky source code panel
+ // on the right. Best for full-bleed detail pages.
+ // - `stacked`: single column — metadata, I/O, then source code in a card with
+ // a capped height. Best for narrower detail panes alongside other content.
layout?: "split" | "stacked";
- /**
- * CSS height for the source code panel. In `split` layout this is the sticky
- * right column's height (defaults to the remaining viewport height under the
- * top nav). In `stacked` layout this caps the inline source card's height.
- */
+ // CSS height for the source code panel. In `split` layout this is the sticky
+ // right column's height (defaults to the remaining viewport height under the
+ // top nav). In `stacked` layout this caps the inline source card's height.
sourcePanelHeight?: string;
- /** Hide the source-authored description when the caller renders its own description panel. */
+ // Hide the source-authored description when the caller renders its own description panel.
hideDescription?: boolean;
}
diff --git a/src/components/shared/CreatedByFilter/CreatedByFilter.tsx b/src/components/shared/CreatedByFilter/CreatedByFilter.tsx
index 7fa3304a5..feb68c239 100644
--- a/src/components/shared/CreatedByFilter/CreatedByFilter.tsx
+++ b/src/components/shared/CreatedByFilter/CreatedByFilter.tsx
@@ -6,13 +6,11 @@ import { Icon } from "@/components/ui/icon";
import { Input } from "@/components/ui/input";
interface CreatedByFilterProps {
- /** Current filter value from URL. undefined means no filter. */
value: string | undefined;
- /** Called when input value changes (parent handles debouncing). */
+ // The parent owns debouncing.
onChange: (value: string | undefined) => void;
- /** Called when user clicks the clear button (for immediate clearing). */
onClear: () => void;
- /** Pre-fills the input on mount and triggers onChange if no URL value is set. */
+ // Pre-fills the input on mount, and fires onChange when the URL carries no value.
defaultValue?: string;
}
diff --git a/src/components/shared/Execution/PipelineIO.tsx b/src/components/shared/Execution/PipelineIO.tsx
index 45804daf7..e3a12b16c 100644
--- a/src/components/shared/Execution/PipelineIO.tsx
+++ b/src/components/shared/Execution/PipelineIO.tsx
@@ -23,11 +23,9 @@ const PipelineIO = ({
section,
}: {
taskArguments?: TaskSpecOutput["arguments"] | null;
- /**
- * When set, renders only one side (without the wrapping ContentBlock) so the
- * caller can supply its own section header (e.g. a collapsible section).
- * When omitted, renders both Inputs/Arguments and Outputs blocks.
- */
+ // When set, renders only one side (without the wrapping ContentBlock) so the
+ // caller can supply its own section header (e.g. a collapsible section).
+ // When omitted, renders both Inputs/Arguments and Outputs blocks.
section?: "inputs" | "outputs";
}) => {
const { setContent } = useContextPanel();
diff --git a/src/components/shared/GitHubLibrary/utils/githubApiClient.ts b/src/components/shared/GitHubLibrary/utils/githubApiClient.ts
index b3713cff2..825f5b219 100644
--- a/src/components/shared/GitHubLibrary/utils/githubApiClient.ts
+++ b/src/components/shared/GitHubLibrary/utils/githubApiClient.ts
@@ -43,9 +43,6 @@ interface GitHubBlob {
node_id: string;
size: number;
url: string;
- /**
- * Decoded content of the blob
- */
content: string;
}
diff --git a/src/components/shared/ReactFlow/FlowSidebar/components/SidebarSection.tsx b/src/components/shared/ReactFlow/FlowSidebar/components/SidebarSection.tsx
index bdd6df7dd..4a76abd28 100644
--- a/src/components/shared/ReactFlow/FlowSidebar/components/SidebarSection.tsx
+++ b/src/components/shared/ReactFlow/FlowSidebar/components/SidebarSection.tsx
@@ -5,13 +5,9 @@ import { Text } from "@/components/ui/typography";
import { cn } from "@/lib/utils";
interface SidebarSectionProps {
- /** Section header title */
title: string;
- /** Optional action element displayed on the right side of the header */
headerAction?: ReactNode;
- /** Section content */
children: ReactNode;
- /** Additional class names for the section container */
className?: string;
}
diff --git a/src/config/aiModels.ts b/src/config/aiModels.ts
index bf5359cb0..984d52ea7 100644
--- a/src/config/aiModels.ts
+++ b/src/config/aiModels.ts
@@ -7,9 +7,9 @@ export interface AiModelOption {
}
interface AiModelOptionsConfig {
- /** Replaces the built-in model suggestions when provided by the host page. */
+ // Replaces the built-in suggestions when the host page provides it.
models?: AiModelOption[];
- /** Suggested model shown first in blank model inputs. */
+ // Shown first in blank model inputs.
defaultModel?: string;
}
diff --git a/src/hooks/useNaturalLanguageComponentSearch.ts b/src/hooks/useNaturalLanguageComponentSearch.ts
index 3d0f3b62e..3c494d46d 100644
--- a/src/hooks/useNaturalLanguageComponentSearch.ts
+++ b/src/hooks/useNaturalLanguageComponentSearch.ts
@@ -13,11 +13,9 @@ import type { ComponentReference } from "@/utils/componentSpec";
interface RerankVariables {
query: string;
candidates: RerankCandidate[];
- /**
- * When true, ask the model to score every candidate (not just the strongest)
- * so every displayed result can show a relevance percentage. Costs more
- * tokens; callers opt in per surface.
- */
+ // When true, ask the model to score every candidate (not just the strongest)
+ // so every displayed result can show a relevance percentage. Costs more
+ // tokens; callers opt in per surface.
scoreAllCandidates?: boolean;
}
diff --git a/src/models/componentSpec/validation/types.ts b/src/models/componentSpec/validation/types.ts
index 013ec039f..39f04a405 100644
--- a/src/models/componentSpec/validation/types.ts
+++ b/src/models/componentSpec/validation/types.ts
@@ -37,6 +37,6 @@ export interface ValidationIssue {
export interface ComponentValidationIssue extends ValidationIssue {
id: string;
subgraphPath: string[];
- /** Name of the entity (task/input/output) for cross-spec lookup. */
+ // Name of the task, input or output, used for cross-spec lookup.
entityName?: string;
}
diff --git a/src/providers/ComponentLibraryProvider/libraries/storage.ts b/src/providers/ComponentLibraryProvider/libraries/storage.ts
index a6e8f002a..71ccd3a60 100644
--- a/src/providers/ComponentLibraryProvider/libraries/storage.ts
+++ b/src/providers/ComponentLibraryProvider/libraries/storage.ts
@@ -19,12 +19,10 @@ interface StoredLibraryFolder {
export interface StoredLibrary extends StoredLibraryFolder {
id: string;
icon?: keyof typeof icons;
- /**
- * yaml - a yaml file that contains the components
- * indexdb - a local database that contains the components. filled only by the Tangle App
- * pinned - a pinned libraries from the Backend API
- * github - a github repository that contains the components
- */
+ // yaml - a yaml file that contains the components
+ // indexdb - a local database that contains the components. filled only by the Tangle App
+ // pinned - a pinned libraries from the Backend API
+ // github - a github repository that contains the components
type: "yaml" | "indexdb" | "pinned" | "github";
configuration?: Record;
diff --git a/src/routes/Dashboard/DashboardComponentsV2View.tsx b/src/routes/Dashboard/DashboardComponentsV2View.tsx
index c716376e5..82c6aff14 100644
--- a/src/routes/Dashboard/DashboardComponentsV2View.tsx
+++ b/src/routes/Dashboard/DashboardComponentsV2View.tsx
@@ -253,11 +253,11 @@ interface ComponentCardProps {
rerankScore?: number;
isAiRanked?: boolean;
isSelected?: boolean;
- /** Position within the current result list — passed to analytics. */
+ // Position within the current result list — passed to analytics.
position?: number;
- /** Whether the user had typed a query when this card was rendered. */
+ // Whether the user had typed a query when this card was rendered.
hadQuery?: boolean;
- /** Whether the detail pane is open, forcing the results list into compact rows. */
+ // Whether the detail pane is open, forcing the results list into compact rows.
isDetailOpen?: boolean;
onSelect: (reference: ComponentReference) => void;
}
diff --git a/src/routes/v2/WINDOWS.md b/src/routes/v2/WINDOWS.md
index f8563799f..2e924e014 100644
--- a/src/routes/v2/WINDOWS.md
+++ b/src/routes/v2/WINDOWS.md
@@ -210,6 +210,26 @@ When undocking:
1. The window ID is removed from the dock area order.
2. `applyUndockState()` restores the pre-dock position/size and clears dock fields.
+## `WindowOptions` Reference
+
+| Option | Default | Meaning |
+| ------------------ | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `id` | generated | Explicit window ID |
+| `title` | required | Title shown in the header |
+| `position` | cascaded from the last window | Initial position |
+| `size` | `DEFAULT_WINDOW_SIZE` | Initial size |
+| `minSize` | `DEFAULT_MIN_SIZE` | Minimum size while resizing |
+| `linkedEntityId` | none | Entity this window belongs to; the window auto-closes when that entity is deleted |
+| `disabledActions` | none | Header actions to hide, e.g. `["close"]` for a non-closable window |
+| `startVisible` | `false` | Open visible even if the persisted state was `hidden` — used by selection-driven windows |
+| `defaultVisible` | `false` | Visibility when there is no persisted state at all |
+| `persisted` | `false` | Persist layout (position, size, dock state) to localStorage across reloads |
+| `defaultDockState` | none (floating) | Dock side for first-time users, before any state is persisted |
+| `variant` | `"window"` | `"panel"` renders chrome-less (border and header appear on hover) and auto-fits its content while floating |
+| `fillDockHeight` | `false` | When docked, grow to fill the remaining dock-area height instead of fitting content — for log-style expanding panels |
+| `onClose` | none | Invoked when the window closes |
+| `miniContent` | none | Compact control (usually an icon button) shown while the dock area is collapsed; clicking it opens the full content in a popover. A window without it is unreachable while its dock area is collapsed |
+
## Opening a Window (Sequence)
```mermaid
@@ -502,9 +522,14 @@ interface WindowContextValue {
| `MAX_DOCK_AREA_WIDTH` | `600px` | Maximum dock column width (resize handle) |
| `COLLAPSED_DOCK_AREA_WIDTH` | `36px` | Width of a collapsed dock column |
| `DEFAULT_DOCKED_HEIGHT` | `300px` | Default height for a docked window |
-| `MIN_DOCKED_HEIGHT` | `100px` | Minimum height for a docked window (resize handle) |
+| `MIN_DOCKED_HEIGHT` | `50px` | Minimum height for a docked window (resize handle) |
+| `DOCKED_HEADER_HEIGHT` | `36px` | Docked window header height, used to stack sticky headers |
+| `WINDOW_CHROME_HEIGHT` | `30px` | Window chrome height |
| `TASK_PANEL_HEIGHT` | `43px` | Height of the TaskPanel bar (used for floating window offset) |
+`DOCK_AREA_RESIZE_SNAP_THRESHOLD` is derived (`MIN_DOCK_AREA_WIDTH - 20`): it is the resize width at
+which a dock area previews and snaps between expanded and collapsed.
+
## Rules and Restrictions
### MobX Integration
diff --git a/src/routes/v2/pages/Editor/components/ContextPanel/components/MultiSelectionDetails/utils.ts b/src/routes/v2/pages/Editor/components/ContextPanel/components/MultiSelectionDetails/utils.ts
index 1f14b19a5..a16f6a9bd 100644
--- a/src/routes/v2/pages/Editor/components/ContextPanel/components/MultiSelectionDetails/utils.ts
+++ b/src/routes/v2/pages/Editor/components/ContextPanel/components/MultiSelectionDetails/utils.ts
@@ -9,10 +9,10 @@ export interface AggregatedArgument {
typeLabel: string;
optional: boolean;
defaultValue?: string;
- /** The shared value across all tasks, or empty string when mixed. */
+ // The shared value across all tasks, or empty string when mixed.
value: string;
isMixed: boolean;
- /** Task IDs that have this input in their component spec. */
+ // Task IDs that have this input in their component spec.
taskIds: string[];
}
diff --git a/src/routes/v2/pages/Editor/components/PinnedTaskContent/PinnedTaskContent.tsx b/src/routes/v2/pages/Editor/components/PinnedTaskContent/PinnedTaskContent.tsx
index 09f1ced26..ea132bfd9 100644
--- a/src/routes/v2/pages/Editor/components/PinnedTaskContent/PinnedTaskContent.tsx
+++ b/src/routes/v2/pages/Editor/components/PinnedTaskContent/PinnedTaskContent.tsx
@@ -13,7 +13,6 @@ import { tracking } from "@/utils/tracking";
import { componentSpecToText } from "@/utils/yaml";
interface PinnedTaskContentProps {
- /** The entity ID of the task to display */
entityId: string;
}
diff --git a/src/routes/v2/shared/components/AiChat/AiChatStoreContext.tsx b/src/routes/v2/shared/components/AiChat/AiChatStoreContext.tsx
index cbd6f9630..d681a63a5 100644
--- a/src/routes/v2/shared/components/AiChat/AiChatStoreContext.tsx
+++ b/src/routes/v2/shared/components/AiChat/AiChatStoreContext.tsx
@@ -12,9 +12,9 @@ import { AiChatStore } from "./aiChatStore";
const AiChatStoreCtx = createRequiredContext("AiChatStoreContext");
interface AiChatStoreProviderProps {
- /** Page-owned factory that spawns the worker for this AI chat. */
+ // Page-owned factory that spawns the worker for this AI chat.
createWorker: () => Worker;
- /** Current page context, baked into each new thread's worker at init. */
+ // Current page context, baked into each new thread's worker at init.
context: AgentContext;
children: ReactNode;
}
diff --git a/src/routes/v2/shared/nodes/TaskNode/TaskNodeCard.tsx b/src/routes/v2/shared/nodes/TaskNode/TaskNodeCard.tsx
index d16c77f67..9d2a5da6e 100644
--- a/src/routes/v2/shared/nodes/TaskNode/TaskNodeCard.tsx
+++ b/src/routes/v2/shared/nodes/TaskNode/TaskNodeCard.tsx
@@ -93,9 +93,9 @@ interface ClassicInputHandleProps {
displayValue: string | undefined;
hideValue?: boolean;
isSecret: boolean;
- /** When true the node has no custom colour, so section chrome follows the theme. */
+ // When true the node has no custom colour, so section chrome follows the theme.
themed: boolean;
- /** Whether the app is in dark mode — used to darken coloured-node chrome. */
+ // Whether the app is in dark mode — used to darken coloured-node chrome.
isDark: boolean;
onInputClick: (name: string, event: ReactMouseEvent) => void;
onHandleClick: (handleId: string, event: ReactMouseEvent) => void;
diff --git a/src/routes/v2/shared/nodes/types.ts b/src/routes/v2/shared/nodes/types.ts
index 33f411e8a..84519d101 100644
--- a/src/routes/v2/shared/nodes/types.ts
+++ b/src/routes/v2/shared/nodes/types.ts
@@ -118,15 +118,13 @@ type DropHandler = (
// ---------------------------------------------------------------------------
export interface NodeTypeManifest {
- /** React Flow node type key (e.g. "task", "input", "output", "conduit", "ghost"). */
+ // React Flow node type key (e.g. "task", "input", "output", "conduit", "ghost").
readonly type: string;
- /** Node ID prefix used to identify this type from an id string. */
+ // Node ID prefix used to identify this type from an id string.
readonly idPrefix: string;
- /**
- * Domain entity type (e.g. "task", "input", "output", "conduit").
- */
+ // Domain entity type (e.g. "task", "input", "output", "conduit").
readonly entityType: string;
hasEntityId?(spec: ComponentSpec, id: string): boolean;
@@ -140,26 +138,20 @@ export interface NodeTypeManifest {
buildNodes(spec: ComponentSpec): Node[];
buildEdges?(spec: ComponentSpec): Edge[];
- /**
- * Transform the base binding edges (e.g. replace "default" edges with
- * a specialised edge type). Called after `buildBindingEdges`.
- */
+ // Transform the base binding edges (e.g. replace "default" edges with
+ // a specialised edge type). Called after `buildBindingEdges`.
transformEdges?(spec: ComponentSpec, edges: Edge[]): Edge[];
// -- Canvas operations ------------------------------------------------
readonly drop?: {
- /** Key in the `application/reactflow` JSON payload. */
+ // Key in the `application/reactflow` JSON payload.
readonly dataKey: string;
handler: DropHandler;
};
- /**
- * Used to get the position of a node from the spec.
- * CopyPaste features rely on this to fetch position of the node regardless of the node type.
- * @param spec
- * @param nodeId
- */
+ // Used to get the position of a node from the spec.
+ // CopyPaste features rely on this to fetch position of the node regardless of the node type.
getPosition(spec: ComponentSpec, nodeId: string): XYPosition | undefined;
updatePosition(
@@ -218,14 +210,12 @@ export interface NodeTypeManifest {
// -- Canvas enhancement (runtime hooks) ---------------------------------
- /**
- * Optional React hook called by the composing `useCanvasEnhancements` hook.
- * Manifests use this to inject extra nodes/edges or transform edges at
- * render time (e.g. ghost-node overlay, conduit edge styling).
- *
- * Safe to use React hooks inside — the manifest array is static so call
- * order is stable across renders.
- */
+ // Optional React hook called by the composing `useCanvasEnhancements` hook.
+ // Manifests use this to inject extra nodes/edges or transform edges at
+ // render time (e.g. ghost-node overlay, conduit edge styling).
+ //
+ // Safe to use React hooks inside — the manifest array is static so call
+ // order is stable across renders.
readonly useCanvasEnhancement?: (
params: CanvasEnhancementParams,
) => CanvasEnhancementResult;
@@ -246,7 +236,7 @@ export interface CanvasEnhancementParams {
export interface CanvasEnhancementResult {
extraNodes?: Node[];
extraEdges?: Edge[];
- /** Replaces the incoming edges (used for styling / type transforms). */
+ // Replaces the incoming edges (used for styling / type transforms).
transformedEdges?: Edge[];
onEdgeClick?: (event: MouseEvent, edge: { id: string }) => void;
}
diff --git a/src/routes/v2/shared/store/keyboardStore.ts b/src/routes/v2/shared/store/keyboardStore.ts
index 12358005f..d87020e1c 100644
--- a/src/routes/v2/shared/store/keyboardStore.ts
+++ b/src/routes/v2/shared/store/keyboardStore.ts
@@ -16,17 +16,15 @@ interface ShortcutDefinition {
id: string;
keys: ShortcutKeys;
label: string;
- /** When true, the shortcut fires even when an input/textarea is focused. */
+ // When true, the shortcut fires even when an input/textarea is focused.
allowInEditable?: boolean;
// todo: add DOM element as a scope for the shortcut
// todo: add enabled: boolean;
- /**
- * Return `false` to allow the native event to propagate:
- * the listener skips preventDefault so the
- * browser / Radix portal handlers (e.g. dialog ESC) can run.
- * The shortcut registry holds at most one handler per combo,
- * so this is NOT a fallthrough to another shortcut.
- */
+ // Return `false` to allow the native event to propagate:
+ // the listener skips preventDefault so the
+ // browser / Radix portal handlers (e.g. dialog ESC) can run.
+ // The shortcut registry holds at most one handler per combo,
+ // so this is NOT a fallthrough to another shortcut.
action: (event: KeyboardEvent, params?: ShortcutParams) => void | false;
}
diff --git a/src/routes/v2/shared/windows/types.ts b/src/routes/v2/shared/windows/types.ts
index 1004f2676..67cf8ca05 100644
--- a/src/routes/v2/shared/windows/types.ts
+++ b/src/routes/v2/shared/windows/types.ts
@@ -1,41 +1,33 @@
import type { ReactNode } from "react";
-/** Window display state */
export type WindowState = "normal" | "maximized" | "minimized" | "hidden";
-/** Actions that can be performed on a window */
export type WindowAction = "close" | "minimize" | "maximize" | "hide";
-/** Docking state for edge snapping */
export type DockState = "left" | "right" | "none";
type DockSide = Exclude;
-/** Type guard: narrows DockState to a concrete dock side ("left" | "right"). */
export function isDockSide(state: DockState): state is DockSide {
return state === "left" || state === "right";
}
-/** Position coordinates */
export interface Position {
x: number;
y: number;
}
-/** Size dimensions */
export interface Size {
width: number;
height: number;
}
-/** Configuration for a dock area column */
export interface DockAreaConfig {
width: number;
collapsed: boolean;
windowOrder: string[];
}
-/** Snap preview types for visual feedback during drag */
export type SnapPreviewType =
| { type: "edge"; side: "left" | "right" }
| {
@@ -47,7 +39,6 @@ export type SnapPreviewType =
areaWidth: number;
};
-/** Reference returned from open() for controlling a window */
export interface WindowRef {
id: string;
close: () => void;
@@ -57,53 +48,27 @@ export interface WindowRef {
restore: () => void;
}
-/** Options for opening a new window */
export interface WindowOptions {
- /** Explicit ID - auto-generated if not provided */
id?: string;
- /** Window title displayed in header */
title: string;
- /** Initial position - defaults to cascaded from last window */
position?: Position;
- /** Initial size - defaults to 320x420 */
size?: Size;
- /** Minimum size for resizing - defaults to 280x200 */
minSize?: Size;
- /** Optional entity ID to link this window to (for auto-close on entity deletion) */
linkedEntityId?: string;
- /** Actions to disable for this window (e.g., ["close"] for non-closable windows) */
disabledActions?: WindowAction[];
- /** If true, window starts visible even if persisted state was hidden. Use for selection-driven windows. */
startVisible?: boolean;
- /** Whether the window is visible when it has no persisted window state. */
defaultVisible?: boolean;
- /** If true, the window's layout (position, size, dock state) is persisted to localStorage across reloads. */
persisted?: boolean;
- /** Default dock side for first-time users (no persisted state). */
defaultDockState?: "left" | "right";
- /** Visual variant. "panel" renders chrome-less (border/header on hover) and auto-fits content when floating. */
variant?: "window" | "panel";
- /**
- * When docked, grow to fill remaining dock-area height instead of fitting
- * content. Useful for panels whose content (e.g. logs) should expand to use
- * all available vertical space.
- */
fillDockHeight?: boolean;
- /** Callback to invoke when the window is closed */
onClose?: () => void;
- /**
- * Optional compact control shown when the window is docked and its dock area
- * is collapsed. Typically an icon button; click opens full content in a popover.
- *
- * NOTE: If omitted, the window is filtered out of the collapsed dock strip and
- * becomes inaccessible while its dock area is collapsed (the user must expand
- * the dock to reach it). Provide `miniContent` for any dockable window that
- * should remain reachable while collapsed.
- */
+ // Without miniContent a window is filtered out of the collapsed dock strip, so
+ // it is unreachable until the user expands the dock area again.
miniContent?: ReactNode;
}
-/** Default window dimensions */
+// All dimensions below are CSS pixels. See src/routes/v2/WINDOWS.md for what each one drives.
export const DEFAULT_WINDOW_SIZE: Size = {
width: 320,
height: 420,
@@ -114,38 +79,26 @@ export const DEFAULT_MIN_SIZE: Size = {
height: 200,
};
-/** Cascade offset for new windows */
export const CASCADE_OFFSET = 24;
-/** Distance from viewport edge to trigger dock preview (px) */
export const EDGE_SNAP_THRESHOLD = 2;
-/** Default dock area width (px) */
export const DEFAULT_DOCK_AREA_WIDTH = 320;
-/** Minimum dock area width (px) */
export const MIN_DOCK_AREA_WIDTH = 220;
-/** Resize width where dock areas preview and snap between expanded and collapsed. */
export const DOCK_AREA_RESIZE_SNAP_THRESHOLD = MIN_DOCK_AREA_WIDTH - 20;
-/** Maximum dock area width (px) */
export const MAX_DOCK_AREA_WIDTH = 600;
-/** Collapsed dock area width (px) */
export const COLLAPSED_DOCK_AREA_WIDTH = 36;
-/** Default height for a docked window (px) */
export const DEFAULT_DOCKED_HEIGHT = 300;
-/** Minimum height for a docked window (px) */
export const MIN_DOCKED_HEIGHT = 50;
-/** Height of a docked window header (px). Used to stack sticky headers. */
export const DOCKED_HEADER_HEIGHT = 36;
-/** Distance from dock area edge to trigger dock insert preview (px) */
export const DOCK_AREA_SNAP_THRESHOLD = 40;
-/** Height of a window chrome (px) */
export const WINDOW_CHROME_HEIGHT = 30;
diff --git a/src/routes/v2/shared/windows/viewPresets.ts b/src/routes/v2/shared/windows/viewPresets.ts
index 7c2ba1310..b0f3e0529 100644
--- a/src/routes/v2/shared/windows/viewPresets.ts
+++ b/src/routes/v2/shared/windows/viewPresets.ts
@@ -8,7 +8,7 @@ export interface ViewPreset {
label: string;
description: string;
visible: Set;
- /** Default dock columns: ids per side, order matters for first-visit layout seeding. */
+ // Window ids per side. Order matters: it seeds the first-visit layout.
dockAreas?: PresetDockAreas;
}
diff --git a/src/services/componentSearchIndex.ts b/src/services/componentSearchIndex.ts
index 0de16f76a..5a9f290a9 100644
--- a/src/services/componentSearchIndex.ts
+++ b/src/services/componentSearchIndex.ts
@@ -17,24 +17,18 @@ import { getComponentName } from "@/utils/getComponentName";
import { expandSynonymTokens } from "./componentSearchSynonyms";
-/** Which field of a component matched the query. Surfaced in the UI. */
export type MatchField =
"name" | "description" | "io" | "implementation" | "metadata";
/**
- * Where a component came from. Attached to every index entry and threaded
- * through to UI cards as a source badge so users know whether a result is
- * from the curated standard library, the backend's published catalog, a
- * registered external library (e.g. GitHub), or their own user components.
+ * Where a component came from. Threaded through to UI cards as a source badge so users can tell a
+ * curated standard-library result from the backend's published catalog, a registered external
+ * library (e.g. GitHub), or their own components.
*/
export interface ComponentSearchSource {
kind: "standard" | "user" | "published" | "registered";
- /** Short label shown in the UI badge (e.g. "Standard", "Published", or a library name). */
label: string;
- /**
- * Stable identifier for future filter chips / URL state. For built-in kinds
- * this matches the kind; for `registered` libraries it's the stored library id.
- */
+ // Matches `kind` for the built-in kinds; for `registered` it is the stored library id.
id: string;
}
@@ -44,19 +38,14 @@ export interface SourcedReference {
}
export interface IndexEntry {
- /** Full reference, kept so callers can render whatever they need. */
reference: ComponentReference;
- /** Component digest. Stable id for round-tripping (LLM rerank, dedupe). */
digest: string;
- /** Display name. */
name: string;
- /** Where this component came from. */
source: ComponentSearchSource;
- /** Normalized searchable text, one per logical field. */
searchable: Record;
- /** Pre-split searchable text, built once with the index to avoid per-keystroke tokenization. */
+ // Derived from `searchable` once at index build time so that tokenizing and
+ // regex-normalizing do not run again on every keystroke.
searchableTokens?: Record;
- /** Phrase-normalized searchable text, built once with the index to avoid per-keystroke regex work. */
searchablePhrases?: Record;
}
@@ -65,7 +54,6 @@ export interface LexicalMatch {
digest: string;
name: string;
source: ComponentSearchSource;
- /** Which fields matched the query (for UX labels like "matched: command"). */
matchedFields: MatchField[];
}
@@ -271,25 +259,22 @@ function extractImplementationText(reference: ComponentReference): string {
return parts.join(" ").toLowerCase();
}
-/**
- * Common projection of a `ComponentReference` into the fields used downstream
- * by both the lexical index and the LLM reranker. Returns `null` when the
- * reference has no digest or no useful metadata — both consumers want to
- * skip such references for the same reason (un-roundtrippable / noise).
- */
export interface ComponentMetadata {
digest: string;
name: string;
- /** Trimmed; empty string when missing. */
description: string;
inputNames: string[];
outputNames: string[];
- /** Names, descriptions, types, and annotations for inputs/outputs. */
+ // Names, descriptions, types and annotations of every input and output, flattened.
ioText: string;
- /** Searchable component-level metadata annotations. */
metadataText: string;
}
+/**
+ * Common projection of a `ComponentReference` into the fields used downstream by both the lexical
+ * index and the LLM reranker. Returns `null` when the reference has no digest or no useful
+ * metadata — both consumers skip such references for the same reason (un-roundtrippable / noise).
+ */
export function extractComponentMetadata(
reference: ComponentReference,
): ComponentMetadata | null {
@@ -336,12 +321,9 @@ const EMPTY_SEARCHABLE: Record = {
};
export interface BuildSearchIndexOptions {
- /**
- * Emit a name-only entry for references that have a digest but no hydrated
- * spec yet, so the panel can offer instant name search before full text is
- * fetched. Off by default: callers passing hydrated refs keep the stricter
- * "skip specs with no useful metadata" behavior.
- */
+ // Emits a name-only entry for references that have a digest but no hydrated spec yet, so the
+ // panel can search names before full text is fetched. Off by default, so callers passing
+ // hydrated refs keep the stricter "skip specs with no useful metadata" behaviour.
includeNameOnly?: boolean;
}
@@ -571,12 +553,8 @@ const FUZZY_SEARCH_FIELDS: MatchField[] = ["name", "io"];
const NEGATIVE_SEARCH_FIELDS: MatchField[] = ["name", "description", "io"];
interface SearchOptions {
- /** Max results to return. Default 20. */
limit?: number;
- /**
- * Minimum query length before any results are returned. Default 1. Set to 2
- * or 3 to suppress noisy results on the first keystroke.
- */
+ // Raise to 2 or 3 to suppress noisy results on the first keystroke.
minLength?: number;
}
diff --git a/src/services/naturalLanguageComponentSearchService.ts b/src/services/naturalLanguageComponentSearchService.ts
index baaf3bb93..bbb743ffd 100644
--- a/src/services/naturalLanguageComponentSearchService.ts
+++ b/src/services/naturalLanguageComponentSearchService.ts
@@ -37,7 +37,7 @@ interface RerankCandidateIO {
}
export interface RerankCandidate {
- /** Component digest. Used to round-trip the model's response to references. */
+ // Round-trips the model's response back to a component reference.
id: string;
name: string;
description: string;
@@ -51,20 +51,20 @@ export interface RerankCandidate {
export interface RerankedMatch {
id: string;
- /** Model-provided relevance, clamped to [0, 1]. */
+ // Model-provided, clamped to [0, 1].
score: number;
reason: string;
}
export interface RerankResult {
matches: RerankedMatch[];
- /** Raw model response, kept for debugging. */
+ // Kept for debugging.
rawContent?: string;
}
export interface ComponentDescriptionResult {
description: string;
- /** Raw model response, kept for debugging. */
+ // Raw model response, kept for debugging.
rawContent?: string;
}
@@ -77,11 +77,11 @@ export class NaturalLanguageSearchConfigError extends Error {
interface LlmOptions {
signal?: AbortSignal;
- /** Optional model id (OpenAI-compatible). Leave blank when the proxy owns selection. */
+ // OpenAI-compatible model id. Leave blank when the proxy owns model selection.
model: string;
- /** Base URL of an OpenAI-compatible API. Required. */
+ // Base URL of an OpenAI-compatible API.
apiBase: string;
- /** Optional bearer token. Leave blank when the proxy owns authentication. */
+ // Bearer token. Leave blank when the proxy owns authentication.
apiKey: string;
}
diff --git a/src/types/aiProvider.ts b/src/types/aiProvider.ts
index d31eaadaa..d691ac63d 100644
--- a/src/types/aiProvider.ts
+++ b/src/types/aiProvider.ts
@@ -1,8 +1,8 @@
export interface AiProviderConfig {
- /** OpenAI-compatible API base URL. Do not include endpoint paths like `/responses`. */
+ // OpenAI-compatible API base URL, with no endpoint path such as `/responses`.
apiBase: string;
- /** Optional bearer token. Leave blank when the proxy owns authentication. */
+ // Leave blank when the proxy owns authentication.
apiKey: string;
- /** Optional generation model id. Leave blank to use the provider default. */
+ // Leave blank to use the provider default.
model: string;
}
diff --git a/src/types/composerSchema.ts b/src/types/composerSchema.ts
index 112d2d884..32dc2dbd3 100644
--- a/src/types/composerSchema.ts
+++ b/src/types/composerSchema.ts
@@ -42,16 +42,12 @@ interface ReplacementDescriptor {
interface BlockDescriptorBase {
id: string;
blockType: BlockType;
- /**
- * Key = placeholder name (e.g., "podName", "startTime"), NOT block ID.
- * Declares which {placeholder} tokens appear in this block's properties.
- */
+ // Key = placeholder name (e.g., "podName", "startTime"), NOT block ID.
+ // Declares which {placeholder} tokens appear in this block's properties.
replacements?: Record;
- /**
- * Whitelist of execution types this block should be displayed for
- * (e.g., ["pod"], ["job"], ["pod", "job"]).
- * If absent or undefined, the block is displayed for all execution types.
- */
+ // Whitelist of execution types this block should be displayed for
+ // (e.g., ["pod"], ["job"], ["pod", "job"]).
+ // If absent or undefined, the block is displayed for all execution types.
displayFor?: string[];
}
diff --git a/src/utils/componentSpec.ts b/src/utils/componentSpec.ts
index ea285bc40..fa38f362d 100644
--- a/src/utils/componentSpec.ts
+++ b/src/utils/componentSpec.ts
@@ -18,9 +18,6 @@ interface InputOutputSpec {
[k: string]: unknown;
};
}
-/**
- * Describes the component input specification
- */
export interface InputSpec extends InputOutputSpec {
name: string;
type?: TypeSpecType;
@@ -32,9 +29,6 @@ export interface InputSpec extends InputOutputSpec {
[k: string]: unknown;
};
}
-/**
- * Describes the component output specification
- */
export interface OutputSpec extends InputOutputSpec {
name: string;
type?: TypeSpecType;
@@ -47,27 +41,18 @@ export interface OutputSpec extends InputOutputSpec {
* Represents the command-line argument placeholder that will be replaced at run-time by the input argument value.
*/
interface InputValuePlaceholder {
- /**
- * Name of the input.
- */
inputValue: string;
}
/**
* Represents the command-line argument placeholder that will be replaced at run-time by a local file path pointing to a file containing the input argument value.
*/
interface InputPathPlaceholder {
- /**
- * Name of the input.
- */
inputPath: string;
}
/**
* Represents the command-line argument placeholder that will be replaced at run-time by a local file path pointing to a file where the program should write its output data.
*/
interface OutputPathPlaceholder {
- /**
- * Name of the output.
- */
outputPath: string;
}
export type StringOrPlaceholder =
@@ -81,25 +66,20 @@ export type StringOrPlaceholder =
* Represents the command-line argument placeholder that will be replaced at run-time by the concatenated values of its items.
*/
interface ConcatPlaceholder {
- /**
- * Items to concatenate
- */
concat: StringOrPlaceholder[];
}
/**
* Represents the command-line argument placeholder that will be replaced at run-time by a boolean value specifying whether the caller has passed an argument for the specified optional input.
*/
interface IsPresentPlaceholder {
- /**
- * Name of the input.
- */
isPresent: string;
}
type IfConditionArgumentType =
IsPresentPlaceholder | boolean | string | InputValuePlaceholder;
type ListOfStringsOrPlaceholders = StringOrPlaceholder[];
/**
- * Represents the command-line argument placeholder that will be replaced at run-time by a boolean value specifying whether the caller has passed an argument for the specified optional input.
+ * Represents the command-line argument placeholder that will be replaced at run-time by either the
+ * `then` or the `else` branch, depending on how `cond` evaluates.
*/
interface IfPlaceholder {
if: {
@@ -109,28 +89,15 @@ interface IfPlaceholder {
};
}
interface ContainerSpec {
- /**
- * Docker image name.
- */
image: string;
- /**
- * Entrypoint array. Not executed within a shell. The docker image's ENTRYPOINT is used if this is not provided.
- */
+ // Not run through a shell. Omitting these falls back to the image's own
+ // ENTRYPOINT and CMD respectively.
command?: StringOrPlaceholder[];
- /**
- * Arguments to the entrypoint. The docker image's CMD is used if this is not provided.
- */
args?: StringOrPlaceholder[];
- /**
- * List of environment variables to set in the container.
- */
env?: {
[k: string]: StringOrPlaceholder;
};
}
-/**
- * Represents the container component implementation.
- */
export interface ContainerImplementation {
container: ContainerSpec;
}
@@ -144,9 +111,6 @@ export interface MetadataSpec {
[FLEX_NODES_ANNOTATION]?: string;
};
}
-/**
- * Component specification. Describes the metadata (name, description, source), the interface (inputs and outputs) and the implementation of the component.
- */
export interface ComponentSpec {
name?: string;
description?: string;
@@ -155,22 +119,15 @@ export interface ComponentSpec {
implementation: ImplementationType;
metadata?: MetadataSpec;
}
-/**
- * Component reference. Contains information that can be used to locate and load a component by name, digest or URL
- */
interface ComponentReferenceBase {
name?: string;
digest?: string;
tag?: string;
url?: string;
spec?: ComponentSpec;
- // Holds unparsed component text. An alternative to spec.
- // url -> data -> text -> spec
- // This simplifies code due to ability to preserve the original component data corresponding to the hash digest.
- // I debated whether to use data (binary) or text here and decided on text.
- // ComponentSpec is usually serialized to YAML or JSON formats that are text based
- // and have better support for text compared to binary data.
- // Not yet in the standard.
+ // Unparsed component source, an alternative to `spec` (url -> data -> text -> spec).
+ // Kept as text so the exact bytes behind the hash digest survive a round trip.
+ // Not yet part of the standard.
text?: string;
}
@@ -376,9 +333,6 @@ export function isDisplayableComponentReference(
* Represents the component argument value that comes from the graph component input.
*/
export interface GraphInputArgument {
- /**
- * References the input of the graph/pipeline.
- */
graphInput: {
inputName: string;
type?: TypeSpecType;
@@ -388,28 +342,16 @@ export interface GraphInputArgument {
* Represents the component argument value that comes from the output of a sibling task.
*/
export interface TaskOutputArgument {
- /**
- * References the output of a sibling task.
- */
taskOutput: {
taskId: string;
outputName: string;
type?: TypeSpecType;
};
}
-/**
- * Reference to a secret by name.
- */
interface SecretReference {
name: string;
}
-/**
- * Represents the component argument value that comes from a secret.
- */
export interface SecretArgument {
- /**
- * References a secret by name.
- */
secret: SecretReference;
}
@@ -421,9 +363,6 @@ type SystemDataArgument = {
[key: string]: Record;
};
-/**
- * Union type for all dynamic data sources.
- */
export type DynamicDataValue = SecretArgument | SystemDataArgument;
export interface DynamicDataArgument {
@@ -433,23 +372,14 @@ export interface DynamicDataArgument {
export type ArgumentType =
string | GraphInputArgument | TaskOutputArgument | DynamicDataArgument;
-/**
- * Pair of operands for a binary operation.
- */
interface TwoArgumentOperands {
op1: ArgumentType;
op2: ArgumentType;
}
-/**
- * Pair of operands for a binary logical operation.
- */
interface TwoLogicalOperands {
op1: PredicateType;
op2: PredicateType;
}
-/**
- * Optional configuration that specifies how the task should be executed. Can be used to set some platform-specific options.
- */
export type PredicateType =
| {
"==": TwoArgumentOperands;
@@ -479,16 +409,11 @@ export type PredicateType =
not: PredicateType;
};
-/**
- * Optional configuration that specifies how the task should be retried if it fails.
- */
interface RetryStrategySpec {
maxRetries?: number;
}
-/**
- * Optional configuration that specifies how the task execution may be skipped if the output data exist in cache.
- */
interface CachingStrategySpec {
+ // When a cached output is younger than this ISO 8601 duration, the task is skipped entirely.
maxCacheStaleness?: string;
}
@@ -497,7 +422,8 @@ export interface ExecutionOptionsSpec {
cachingStrategy?: CachingStrategySpec;
}
/**
- * 'Task specification. Task is a configured component - a component supplied with arguments and other applied configuration changes.
+ * A task is a configured component: a component supplied with arguments and other applied
+ * configuration changes.
*/
export interface TaskSpec {
componentRef: ComponentReference;
@@ -510,9 +436,6 @@ export interface TaskSpec {
[k: string]: unknown;
};
}
-/**
- * Describes the graph component implementation. It represents a graph of component tasks connected to the upstream sources of data using the argument specifications. It also describes the sources of graph output values.
- */
export interface GraphSpec {
tasks: {
[k: string]: TaskSpec;
@@ -521,14 +444,10 @@ export interface GraphSpec {
[k: string]: TaskOutputArgument;
};
}
-/**
- * Represents the graph component implementation.
- */
export interface GraphImplementation {
graph: GraphSpec;
}
-// Type guards
export const isValidComponentSpec = (obj: any): obj is ComponentSpec =>
typeof obj === "object" && "implementation" in obj;
@@ -564,17 +483,11 @@ export const isGraphInputArgument = (
): arg is GraphInputArgument =>
typeof arg === "object" && arg !== null && "graphInput" in arg;
-/**
- * Checks if an argument is any type of dynamic data argument.
- */
export const isDynamicDataArgument = (
arg?: ArgumentType,
): arg is DynamicDataArgument =>
typeof arg === "object" && arg !== null && "dynamicData" in arg;
-/**
- * Checks if an argument is a secret-based dynamic data argument.
- */
export const isSecretArgument = (
arg?: ArgumentType,
): arg is DynamicDataArgument =>