Unverified 提交 f03bf922 authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

Refactor Dyad card components to use new DyadCardPrimitives (#2482)

## Summary This PR introduces a new `DyadCardPrimitives` component library and refactors all Dyad markdown action cards to use these reusable primitives. This improves consistency, reduces code duplication, and makes styling changes easier to maintain across the codebase. ## Key Changes - **New `DyadCardPrimitives.tsx`**: Created a comprehensive library of reusable card components including: - `DyadCard`: Main container with accent color support and state indicators - `DyadCardHeader`: Header row with icon and flexible content area - `DyadBadge`: Small pill badges for card type labels - `DyadExpandIcon`: Animated chevron for expand/collapse - `DyadStateIndicator`: Spinner/X/checkmark for pending/aborted/finished states - `DyadCardContent`: Expandable content area with smooth animations - `DyadFilePath`, `DyadDescription`, `DyadFinishedIcon`: Additional utility components - Support for 11 accent colors (blue, purple, violet, red, amber, green, emerald, teal, sky, indigo, slate) - **Refactored components** to use new primitives: - `DyadAddDependency`: Simplified layout, improved package display styling - `DyadAddIntegration`: Consistent card styling for both pending and completed states - `DyadCodeSearch`: Cleaner header with state indicator - `DyadCodeSearchResult`: Standardized file list display - `DyadCodebaseContext`: Unified expand/collapse behavior - `DyadDatabaseSchema`: Simplified header structure - `DyadDelete`: Consistent red accent styling - `DyadEdit`: Improved state indicators and layout - `DyadExecuteSql`: Cleaner SQL badge and state handling - And 19+ additional components ## Implementation Details - All components now use semantic color tokens (`foreground`, `muted-foreground`, `background-lightest`, etc.) for better dark mode support - Consistent spacing and sizing across all cards (px-3, py-2 for headers, px-3 pb-3 for content) - Smooth transitions for expand/collapse animations using `max-h` and `opacity` - Left border accent (3px) appears only when card is in pending or aborted state - Removed inline icon imports from individual components; now centralized in primitives - Improved accessibility with proper semantic HTML and ARIA attributes https://claude.ai/code/session_01AAvVLShqeRjs42LhUdeK3e <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/2482"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Large UI refactor across many chat card components and accessibility attributes; primary risk is visual/interaction regressions and snapshot/test churn rather than data/security impact. > > **Overview** > **Refactors Dyad chat “action cards” to a shared component library.** Introduces `DyadCardPrimitives` (e.g., `DyadCard`, `DyadCardHeader`, `DyadBadge`, `DyadStateIndicator`, `DyadCardContent`) and migrates many markdown-rendered cards (edit/write/delete/grep/logs/list-files/code-search, integrations, etc.) to use these primitives for consistent styling, expand/collapse behavior, and keyboard/ARIA interaction. > > Updates E2E ARIA snapshots to match the new card structure and accessibility tree, slightly tweaks chat message typography, bumps `@playwright/test` to `^1.58.2`, and expands `lint-staged` formatting (`oxfmt`) to include `*.json`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e020627fdb8955d948d982cb525c929b0610ee77. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Refactored all Dyad markdown action cards to use new DyadCardPrimitives for a consistent, accessible card UI with smoother interactions and easier styling. Adds lazy-mounted content and keyboard-accessible expand/collapse, reduces duplication, and improves dark mode across 28+ components. - **New Features** - Added DyadCardPrimitives: DyadCard, DyadCardHeader, DyadBadge, DyadExpandIcon, DyadStateIndicator (with finished state), DyadCardContent, plus helpers (DyadFilePath, DyadDescription, DyadFinishedIcon). - 11 accent colors with tinted icon circles and a left accent border shown for pending/aborted states; showAccent prop for explicit control. - Smooth expand/collapse using CSS grid with a rotating chevron; DyadCardContent lazy-mounts heavy children. - Semantic color tokens for reliable light/dark theming. - Improved accessibility: role="button", tabIndex, Enter/Space handlers, and aria-expanded on interactive cards. - **Refactors** - Migrated 28+ markdown action cards (e.g., AddDependency, CodeSearch, Edit, ExecuteSql, Status, WebSearch, Write) to the primitives; unified headers, badges, spacing, state indicators, and expand/collapse behavior. - Fixed regressions: correct state extraction (Delete/Rename/Output), restored children rendering (Delete/Read/Rename), passed isExpanded to all interactive cards, click propagation fix in Grep. - Removed per-component icon imports and ad‑hoc styles; net reduction of ~480 LOC. <sup>Written for commit e020627fdb8955d948d982cb525c929b0610ee77. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: 's avatarClaude <noreply@anthropic.com>
上级 1516181e
- paragraph: tc=write-index - paragraph: tc=write-index
- paragraph: OK, I'm going to do some writing now... - paragraph: OK, I'm going to do some writing now...
- 'button "Index.tsx src/pages/Index.tsx Edit Summary: write-description"':
- img
- button "Edit":
- img
- img
- paragraph: And it's done!
- button "Copy":
- img
- img - img
- text: Index.tsx - text: test-model
- img - img
- text: "src/pages/Index.tsx Summary: write-description" - text: less than a minute ago
- paragraph: And it's done! - button "Undo":
- img
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
- paragraph: tc=write-index - paragraph: tc=write-index
- paragraph: OK, I'm going to do some writing now... - paragraph: OK, I'm going to do some writing now...
- img - 'button "Index.tsx src/pages/Index.tsx Edit Summary: write-description"':
- text: Index.tsx
- button "Edit":
- img - img
- img - text: ""
- text: "src/pages/Index.tsx Summary: write-description" - button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: And it's done! - paragraph: And it's done!
- button "Copy": - button "Copy":
- img - img
- img - img
- text: Approved
- img
- text: test-model - text: test-model
- img - img
- text: less than a minute ago - text: less than a minute ago
- img
- text: wrote 1 file(s)
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: basic - paragraph: basic
- img - button "file1.txt file1.txt Edit":
- text: file1.txt - img
- img - text: ""
- text: file1.txt - button "Edit":
- img
- text: ""
- img
- paragraph: More EOM - paragraph: More EOM
- button "Copy":
- img
- img - img
- text: Approved - text: Approved
- img
- text: test-model
- img
- text: less than a minute ago
- img
- text: wrote 1 file(s)
- paragraph: "[[UPLOAD_IMAGE_TO_CODEBASE]]" - paragraph: "[[UPLOAD_IMAGE_TO_CODEBASE]]"
- paragraph: "Attachments:" - paragraph: "Attachments:"
- paragraph: "File to upload to codebase: logo.png (file id: DYAD_ATTACHMENT_0)" - paragraph: "File to upload to codebase: logo.png (file id: DYAD_ATTACHMENT_0)"
- paragraph: Uploading image to codebase - paragraph: Uploading image to codebase
- img - 'button "file.png new/image/file.png Edit Summary: Uploaded image to codebase"':
- text: file.png - img
- img - text: ""
- text: "new/image/file.png Summary: Uploaded image to codebase" - button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- button "Copy":
- img
- img - img
- text: Approved - text: Approved
- img
- text: test-model
- img
- text: less than a minute ago
- img
- text: wrote 1 file(s)
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: basic - paragraph: basic
- img - button "file1.txt file1.txt Edit"
- text: file1.txt
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- img - img
- text: Approved - text: Approved
...@@ -14,4 +11,4 @@ ...@@ -14,4 +11,4 @@
- img - img
- text: Approved - text: Approved
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
- paragraph: basic - paragraph: basic
- img - button "file1.txt file1.txt Edit"
- text: file1.txt
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- img - img
- text: Approved - text: Approved
...@@ -14,4 +11,4 @@ ...@@ -14,4 +11,4 @@
- img - img
- text: Approved - text: Approved
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
- paragraph: tc=write-index - paragraph: tc=write-index
- paragraph: OK, I'm going to do some writing now... - paragraph: OK, I'm going to do some writing now...
- img - 'button "Index.tsx src/pages/Index.tsx Edit Summary: write-description"':
- text: Index.tsx - img
- img - text: ""
- text: "src/pages/Index.tsx Summary: write-description" - button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: And it's done! - paragraph: And it's done!
- button "Copy":
- img
- img - img
- text: Approved - text: Approved
- img
- text: test-model
- img
- text: less than a minute ago
- img
- text: wrote 1 file(s)
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- region "Notifications (F8)": - region "Notifications (F8)":
- list - list
- region "Notifications alt+T" - region "Notifications alt+T"
- text: Testing:write-index! - text: Testing:write-index!
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit"
- text: file1.txt
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- img - img
- text: Approved - text: Approved
- paragraph: "[dump] hi" - paragraph: "[dump] hi"
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit"
- text: file1.txt
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- img - img
- text: Approved - text: Approved
...@@ -11,4 +8,4 @@ ...@@ -11,4 +8,4 @@
- img - img
- text: Approved - text: Approved
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
- paragraph: Create a utility function in src/utils/helper.ts - paragraph: Create a utility function in src/utils/helper.ts
- img - button "file1.txt file1.txt Cancel"
- text: file1.txt - text: typescript
- button "Edit":
- img
- img
- text: file1.txt typescript
- button "Copy": - button "Copy":
- img - img
- paragraph: More EOM - paragraph: More EOM
...@@ -17,4 +13,4 @@ ...@@ -17,4 +13,4 @@
- button "Undo": - button "Undo":
- img - img
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
- paragraph: Create a simple React component in src/components/Hello.tsx - paragraph: Create a simple React component in src/components/Hello.tsx
- img - button "file1.txt file1.txt Cancel" [aria-expanded="true"]
- text: file1.txt
- button "Cancel":
- img
- img
- text: file1.txt file1.txt
- button [disabled]: - button [disabled]:
- img - img
- img - img
...@@ -21,4 +16,4 @@ ...@@ -21,4 +16,4 @@
- button "Undo": - button "Undo":
- img - img
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit"
- text: file1.txt
- button "Edit":
- img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button "Copy": - button "Copy":
- img - img
...@@ -68,4 +63,4 @@ ...@@ -68,4 +63,4 @@
- button "Undo": - button "Undo":
- img - img
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
- paragraph: Summarize from chat-id=1 - paragraph: Summarize from chat-id=1
- img - button "file1.txt file1.txt Edit"
- text: file1.txt
- button "Edit":
- img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button "Copy": - button "Copy":
- img - img
...@@ -15,4 +10,4 @@ ...@@ -15,4 +10,4 @@
- button "Undo": - button "Undo":
- img - img
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
- paragraph: tc=create-multiple-errors - paragraph: tc=create-multiple-errors
- paragraph: I will intentionally add multiple errors to test the Fix All Errors button - paragraph: I will intentionally add multiple errors to test the Fix All Errors button
- img - 'button "Index.tsx src/pages/Index.tsx Edit Summary: intentionally add first error"':
- text: Index.tsx
- button "Edit":
- img - img
- img - text: ""
- text: "src/pages/Index.tsx Summary: intentionally add first error" - button "Edit":
- img - img
- text: Error First error in Index... - text: ""
- img
- button "Copy":
- img - img
- button "Fix with AI": - text: ""
- button "Error First error in Index Copy Fix with AI":
- img - img
- img - text: ""
- text: ErrorComponent.tsx
- button "Edit":
- img - img
- img - button "Copy":
- text: "src/components/ErrorComponent.tsx Summary: intentionally add second error" - img
- img - text: ""
- text: Error Second error in ErrorComponent... - button "Fix with AI":
- img - img
- button "Copy": - text: ""
- 'button "ErrorComponent.tsx src/components/ErrorComponent.tsx Edit Summary: intentionally add second error"':
- img - img
- button "Fix with AI": - text: ""
- button "Edit":
- img
- text: ""
- img - img
- img - text: ""
- text: helper.ts - button "Error Second error in ErrorComponent Copy Fix with AI":
- button "Edit":
- img - img
- img - text: ""
- text: "src/utils/helper.ts Summary: intentionally add third error" - img
- img - button "Copy":
- text: Error Third error in helper... - img
- img - text: ""
- button "Copy": - button "Fix with AI":
- img
- text: ""
- 'button "helper.ts src/utils/helper.ts Edit Summary: intentionally add third error"':
- img
- text: ""
- button "Edit":
- img
- text: ""
- img - img
- button "Fix with AI": - text: ""
- button "Error Third error in helper Copy Fix with AI":
- img - img
- text: ""
- img
- button "Copy":
- img
- text: ""
- button "Fix with AI":
- img
- text: ""
- button "Fix All Errors (3)": - button "Fix All Errors (3)":
- img - img
- button: - text: ""
- button "Copy":
- img - img
- img - img
- text: Approved - text: Approved
- img - img
- text: test-model
- img
- text: less than a minute ago - text: less than a minute ago
- img - img
- text: wrote 3 file(s) - text: wrote 3 file(s)
...@@ -54,22 +72,27 @@ ...@@ -54,22 +72,27 @@
- listitem: First error in Index - listitem: First error in Index
- listitem: Second error in ErrorComponent - listitem: Second error in ErrorComponent
- listitem: Third error in helper - listitem: Third error in helper
- img - button "file1.txt file1.txt Edit":
- text: file1.txt - img
- button "Edit": - text: ""
- button "Edit":
- img
- text: ""
- img - img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button: - button "Copy":
- img - img
- img - img
- text: Approved - text: Approved
- img - img
- text: test-model
- img
- text: less than a minute ago - text: less than a minute ago
- img - img
- text: wrote 1 file(s) - text: wrote 1 file(s)
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
...@@ -5,4 +5,4 @@ ...@@ -5,4 +5,4 @@
- img - img
- text: "Tip: Check if restarting the app fixes the error." - text: "Tip: Check if restarting the app fixes the error."
- button "Fix error with AI": - button "Fix error with AI":
- img - img
\ No newline at end of file
...@@ -5,4 +5,4 @@ ...@@ -5,4 +5,4 @@
- img - img
- text: "Tip: Check if restarting the app fixes the error." - text: "Tip: Check if restarting the app fixes the error."
- button "Fix error with AI": - button "Fix error with AI":
- img - img
\ No newline at end of file
- paragraph: tc=create-error - paragraph: tc=create-error
- paragraph: I will intentionally add an error - paragraph: I will intentionally add an error
- img - 'button "Index.tsx src/pages/Index.tsx Edit Summary: intentionally add an error"':
- text: Index.tsx
- button "Edit":
- img - img
- img - text: ""
- text: "src/pages/Index.tsx Summary: intentionally add an error" - button "Edit":
- button: - img
- text: ""
- img
- text: ""
- button "Copy":
- img - img
- img - img
- text: Approved - text: Approved
...@@ -19,17 +21,18 @@ ...@@ -19,17 +21,18 @@
- paragraph: - paragraph:
- text: "Fix error: Error Line 6 error Stack trace: Index (" - text: "Fix error: Error Line 6 error Stack trace: Index ("
- link /http:\/\/localhost:\d+\/src\/pages\/Index\.tsx:6:6/: - link /http:\/\/localhost:\d+\/src\/pages\/Index\.tsx:6:6/:
- /url: /http:\/\/localhost:\d+\/src\/pages\/Index\.tsx:6:6/ - /url: http://localhost:53524/src/pages/Index.tsx:6:6
- text: ) - text: )
- text: plaintext - text: plaintext
- code: Fixing the error... - code: Fixing the error...
- img - button "Index.tsx src/pages/Index.tsx Edit":
- text: Index.tsx
- button "Edit":
- img - img
- img - text: ""
- text: src/pages/Index.tsx - button "Edit":
- button: - img
- text: ""
- img
- button "Copy":
- img - img
- img - img
- text: Approved - text: Approved
...@@ -41,5 +44,7 @@ ...@@ -41,5 +44,7 @@
- text: wrote 1 file(s) - text: wrote 1 file(s)
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- region "Notifications (F8)":
- list
- region "Notifications alt+T"
- heading "No more errors!" [level=1]
- link "Made with Dyad":
- /url: https://www.dyad.sh/
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit"
- text: file1.txt
- button "Edit":
- img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button: - button:
- img - img
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit"
- text: file1.txt
- button "Edit":
- img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button: - button:
- img - img
- img - img
- text: less than a minute ago - text: less than a minute ago
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- button "file1.txt file1.txt Edit":
- img
- text: ""
- button "Edit":
- img
- text: ""
- img
- paragraph: More EOM
- button "Copy":
- img
- img - img
- text: file1.txt - text: test-model
- img - img
- text: file1.txt - text: less than a minute ago
- paragraph: More EOM - button "Undo":
- img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: hi - paragraph: hi
- button "file1.txt file1.txt Edit":
- img
- text: ""
- button "Edit":
- img
- text: ""
- img
- paragraph: More EOM
- button "Copy":
- img
- img - img
- text: file1.txt - text: lmstudio-model-1
- img - img
- text: file1.txt - text: less than a minute ago
- paragraph: More EOM - button "Undo":
- img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit":
- text: file1.txt - img
- button "Edit": - text: ""
- button "Edit":
- img
- text: ""
- img - img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button "Copy": - button "Copy":
- img - img
...@@ -16,19 +17,23 @@ ...@@ -16,19 +17,23 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- paragraph: tc=local-agent/mcp-calculator - paragraph: tc=local-agent/mcp-calculator
- paragraph: I'll calculate the sum of 5 and 3 using the calculator. - paragraph: I'll calculate the sum of 5 and 3 using the calculator.
- img - button "Tool Call testing-mcp-server calculator_add":
- text: Tool Call - img
- img - text: ""
- text: testing-mcp-server calculator_add
- img
- text: "Error MCP tool 'testing-mcp-server__calculator_add' failed: keyValidator._parse is not a function..."
- img
- button "Copy":
- img - img
- button "Fix with AI": - 'button "Error MCP tool ''testing-mcp-server__calculator_add'' failed: keyValidator._parse is not a function Copy Fix with AI"':
- img - img
- text: ""
- img
- button "Copy":
- img
- text: ""
- button "Fix with AI":
- img
- text: ""
- paragraph: The sum of 5 and 3 is 8. The calculation was performed successfully using the MCP calculator tool. - paragraph: The sum of 5 and 3 is 8. The calculation was performed successfully using the MCP calculator tool.
- button "Copy": - button "Copy":
- img - img
...@@ -38,7 +43,10 @@ ...@@ -38,7 +43,10 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit":
- text: file1.txt - img
- button "Edit": - text: ""
- button "Edit":
- img
- text: ""
- img - img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button "Copy": - button "Copy":
- img - img
...@@ -16,10 +17,11 @@ ...@@ -16,10 +17,11 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- paragraph: tc=local-agent/ask-read-file - paragraph: tc=local-agent/ask-read-file
- paragraph: Let me read the file to explain its contents. - paragraph: Let me read the file to explain its contents.
- img - img
- text: App.tsx Read src/App.tsx - text: Read src/App.tsx
- paragraph: This is a simple React component that renders a div with the text 'Minimal imported app'. The component is exported as the default export. - paragraph: This is a simple React component that renders a div with the text 'Minimal imported app'. The component is exported as the default export.
- button "Copy": - button "Copy":
- img - img
...@@ -29,7 +31,10 @@ ...@@ -29,7 +31,10 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit":
- text: file1.txt - img
- button "Edit": - text: ""
- button "Edit":
- img
- text: ""
- img - img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button "Copy": - button "Copy":
- img - img
...@@ -16,20 +17,25 @@ ...@@ -16,20 +17,25 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- paragraph: tc=local-agent/parallel-tools - paragraph: tc=local-agent/parallel-tools
- paragraph: I'll create two files for you in parallel. - paragraph: I'll create two files for you in parallel.
- img - 'button "math.ts src/utils/math.ts Edit Summary: Create math utilities"':
- text: math.ts
- button "Edit":
- img - img
- img - text: ""
- text: "src/utils/math.ts Summary: Create math utilities" - button "Edit":
- img - img
- text: string.ts - text: ""
- button "Edit":
- img - img
- img - text: ""
- text: "src/utils/string.ts Summary: Create string utilities" - 'button "string.ts src/utils/string.ts Edit Summary: Create string utilities"':
- img
- text: ""
- button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: Task completed. - paragraph: Task completed.
- button "Copy": - button "Copy":
- img - img
...@@ -39,7 +45,10 @@ ...@@ -39,7 +45,10 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit":
- text: file1.txt - img
- button "Edit": - text: ""
- button "Edit":
- img
- text: ""
- img - img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button "Copy": - button "Copy":
- img - img
...@@ -16,15 +17,16 @@ ...@@ -16,15 +17,16 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- paragraph: tc=local-agent/read-then-edit - paragraph: tc=local-agent/read-then-edit
- paragraph: Let me first read the current file contents to understand what we're working with. - paragraph: Let me first read the current file contents to understand what we're working with.
- img - img
- text: App.tsx Read src/App.tsx - text: Read src/App.tsx
- paragraph: Now I'll update the welcome message to say Hello World instead. - paragraph: Now I'll update the welcome message to say Hello World instead.
- img - button "App.tsx src/App.tsx Turbo Edit":
- text: Turbo Edit App.tsx - img
- img - text: ""
- text: src/App.tsx - img
- paragraph: Done! I've updated the title from 'Minimal imported app' to 'UPDATED imported app'. The change has been applied successfully. - paragraph: Done! I've updated the title from 'Minimal imported app' to 'UPDATED imported app'. The change has been applied successfully.
- button "Copy": - button "Copy":
- img - img
...@@ -34,7 +36,10 @@ ...@@ -34,7 +36,10 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit":
- text: file1.txt - img
- button "Edit": - text: ""
- button "Edit":
- img
- text: ""
- img - img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button "Copy": - button "Copy":
- img - img
...@@ -16,10 +17,12 @@ ...@@ -16,10 +17,12 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- paragraph: tc=local-agent/code-search - paragraph: tc=local-agent/code-search
- paragraph: I'll search for files related to React components in the codebase. - paragraph: I'll search for files related to React components in the codebase.
- 'button "Code Search React component rendering Query: React component rendering Results: - .gitignore - index.html - package.json"': - button "Code Search React component rendering":
- img - img
- text: ""
- img - img
- paragraph: I found the relevant files! The main React component is in src/App.tsx which handles the app rendering. - paragraph: I found the relevant files! The main React component is in src/App.tsx which handles the app rendering.
- button "Copy": - button "Copy":
...@@ -30,7 +33,10 @@ ...@@ -30,7 +33,10 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit":
- text: file1.txt - img
- button "Edit": - text: ""
- button "Edit":
- img
- text: ""
- img - img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button "Copy": - button "Copy":
- img - img
...@@ -16,10 +17,11 @@ ...@@ -16,10 +17,11 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- paragraph: tc=local-agent/add-dependency - paragraph: tc=local-agent/add-dependency
- paragraph: I'll add a dependency to your project. - paragraph: I'll add a dependency to your project.
- img - img
- text: Do you want to install these packages? @dyad-sh/supabase-management-js Make sure these packages are what you want. - text: Add Packages Do you want to install these packages? @dyad-sh/supabase-management-js Make sure these packages are what you want.
- paragraph: Dependency added done. - paragraph: Dependency added done.
- button "Copy": - button "Copy":
- img - img
...@@ -29,7 +31,10 @@ ...@@ -29,7 +31,10 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit":
- text: file1.txt - img
- button "Edit": - text: ""
- button "Edit":
- img
- text: ""
- img - img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button "Copy": - button "Copy":
- img - img
...@@ -16,10 +17,11 @@ ...@@ -16,10 +17,11 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- paragraph: tc=local-agent/add-dependency - paragraph: tc=local-agent/add-dependency
- paragraph: I'll add a dependency to your project. - paragraph: I'll add a dependency to your project.
- img - img
- text: Do you want to install these packages? @dyad-sh/supabase-management-js Make sure these packages are what you want. - text: Add Packages Do you want to install these packages? @dyad-sh/supabase-management-js Make sure these packages are what you want.
- paragraph: Dependency added done. - paragraph: Dependency added done.
- button "Copy": - button "Copy":
- img - img
...@@ -29,7 +31,10 @@ ...@@ -29,7 +31,10 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit":
- text: file1.txt - img
- button "Edit": - text: ""
- button "Edit":
- img
- text: ""
- img - img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button "Copy": - button "Copy":
- img - img
...@@ -16,17 +17,21 @@ ...@@ -16,17 +17,21 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- paragraph: tc=local-agent/add-dependency - paragraph: tc=local-agent/add-dependency
- paragraph: I'll add a dependency to your project. - paragraph: I'll add a dependency to your project.
- img - img
- text: Do you want to install these packages? @dyad-sh/supabase-management-js Make sure these packages are what you want. - text: Add Packages Do you want to install these packages? @dyad-sh/supabase-management-js Make sure these packages are what you want.
- img - 'button "Error Tool ''add_dependency'' failed: User denied permission for add_dependency Copy Fix with AI"':
- text: "Error Tool 'add_dependency' failed: User denied permission for add_dependency..."
- img
- button "Copy":
- img - img
- button "Fix with AI": - text: ""
- img - img
- button "Copy":
- img
- text: ""
- button "Fix with AI":
- img
- text: ""
- paragraph: Dependency added done. - paragraph: Dependency added done.
- button "Copy": - button "Copy":
- img - img
...@@ -36,7 +41,10 @@ ...@@ -36,7 +41,10 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit":
- text: file1.txt - img
- button "Edit": - text: ""
- button "Edit":
- img
- text: ""
- img - img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button "Copy": - button "Copy":
- img - img
...@@ -16,16 +17,19 @@ ...@@ -16,16 +17,19 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- paragraph: tc=local-agent/upload-to-codebase - paragraph: tc=local-agent/upload-to-codebase
- paragraph: "Attachments:" - paragraph: "Attachments:"
- paragraph: "File to upload to codebase: logo.png (file id: DYAD_ATTACHMENT_0)" - paragraph: "File to upload to codebase: logo.png (file id: DYAD_ATTACHMENT_0)"
- paragraph: I'll upload your file to the codebase. - paragraph: I'll upload your file to the codebase.
- img - 'button "uploaded-file.png assets/uploaded-file.png Edit Summary: Upload file to codebase"':
- text: uploaded-file.png
- button "Edit":
- img - img
- img - text: ""
- text: "assets/uploaded-file.png Summary: Upload file to codebase" - button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: I've successfully uploaded your file to assets/uploaded-file.png in the codebase. - paragraph: I've successfully uploaded your file to assets/uploaded-file.png in the codebase.
- button "Copy": - button "Copy":
- img - img
...@@ -35,7 +39,10 @@ ...@@ -35,7 +39,10 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit":
- text: file1.txt - img
- button "Edit": - text: ""
- button "Edit":
- img
- text: ""
- img - img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button "Copy": - button "Copy":
- img - img
...@@ -16,25 +17,30 @@ ...@@ -16,25 +17,30 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- paragraph: tc=local-agent/grep-search - paragraph: tc=local-agent/grep-search
- paragraph: I'll search for 'createRoot' in the codebase to find where the React app is initialized. - paragraph: I'll search for 'createRoot' in the codebase to find where the React app is initialized.
- img - 'button "GREP \"createRoot\" (2 matches) log Copy log src/main.tsx:1: import { createRoot } from \"react-dom/client\"; src/main.tsx:4: createRoot(document.getElementById(\"root\")!).render(<App />);" [expanded]':
- text: GREP"createRoot"(2 matches)
- img
- text: log
- button "Copy":
- img - img
- text: log - text: ""
- code: "src/main.tsx:1: import { createRoot } from \"react-dom/client\"; src/main.tsx:4: createRoot(document.getElementById(\"root\")!).render(<App />);" - img
- text: ""
- button "Copy":
- img
- text: ""
- text: ""
- code: "src/main.tsx:1: import { createRoot } from \"react-dom/client\"; src/main.tsx:4: createRoot(document.getElementById(\"root\")!).render(<App />);"
- paragraph: Now I'll search specifically in .tsx files for 'App' to find component references. - paragraph: Now I'll search specifically in .tsx files for 'App' to find component references.
- img - 'button "GREP \"App\" in *.tsx (4 matches) log Copy log src/App.tsx:1: const App = () => <div>Minimal imported app</div>; src/App.tsx:3: export default App; src/main.tsx:2: import App from \"./App.tsx\"; src/main.tsx:4: createRoot(document.getElementById(\"root\")!).render(<App />);" [expanded]':
- text: GREP"App" in *.tsx(4 matches) - img
- img - text: ""
- text: log
- button "Copy":
- img - img
- text: log - text: ""
- code: "src/App.tsx:1: const App = () => <div>Minimal imported app</div>; src/App.tsx:3: export default App; src/main.tsx:2: import App from \"./App.tsx\"; src/main.tsx:4: createRoot(document.getElementById(\"root\")!).render(<App />);" - button "Copy":
- img
- text: ""
- text: ""
- code: "src/App.tsx:1: const App = () => <div>Minimal imported app</div>; src/App.tsx:3: export default App; src/main.tsx:2: import App from \"./App.tsx\"; src/main.tsx:4: createRoot(document.getElementById(\"root\")!).render(<App />);"
- paragraph: I found the matches! The React app is initialized in src/main.tsx using createRoot, and the App component is defined in src/App.tsx and imported in src/main.tsx. - paragraph: I found the matches! The React app is initialized in src/main.tsx using createRoot, and the App component is defined in src/App.tsx and imported in src/main.tsx.
- button "Copy": - button "Copy":
- img - img
...@@ -44,7 +50,10 @@ ...@@ -44,7 +50,10 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- img - 'button "GREP \"createRoot\" (2 matches) log Copy log src/main.tsx:1: import { createRoot } from \"react-dom/client\"; src/main.tsx:4: createRoot(document.getElementById(\"root\")!).render(<App />);" [expanded]':
- text: GREP"createRoot"(2 matches)
- img
- text: log
- button "Copy":
- img - img
- text: log - text: ""
- code: "src/main.tsx:1: import { createRoot } from \"react-dom/client\"; src/main.tsx:4: createRoot(document.getElementById(\"root\")!).render(<App />);" - img
\ No newline at end of file - text: ""
- button "Copy":
- img
- text: ""
- text: ""
- code: "src/main.tsx:1: import { createRoot } from \"react-dom/client\"; src/main.tsx:4: createRoot(document.getElementById(\"root\")!).render(<App />);"
\ No newline at end of file
- img - 'button "GREP \"App\" in *.tsx (4 matches) log Copy log src/App.tsx:1: const App = () => <div>Minimal imported app</div>; src/App.tsx:3: export default App; src/main.tsx:2: import App from \"./App.tsx\"; src/main.tsx:4: createRoot(document.getElementById(\"root\")!).render(<App />);" [expanded]':
- text: GREP"App" in *.tsx(4 matches)
- img
- text: log
- button "Copy":
- img - img
- text: log - text: ""
- code: "src/App.tsx:1: const App = () => <div>Minimal imported app</div>; src/App.tsx:3: export default App; src/main.tsx:2: import App from \"./App.tsx\"; src/main.tsx:4: createRoot(document.getElementById(\"root\")!).render(<App />);" - img
\ No newline at end of file - text: ""
- button "Copy":
- img
- text: ""
- text: ""
- code: "src/App.tsx:1: const App = () => <div>Minimal imported app</div>; src/App.tsx:3: export default App; src/main.tsx:2: import App from \"./App.tsx\"; src/main.tsx:4: createRoot(document.getElementById(\"root\")!).render(<App />);"
\ No newline at end of file
- 'button "List Files: src"': - button "src - src/App.tsx - src/main.tsx - src/vite-env.d.ts (3 files total)" [expanded]:
- img - img
- text: ""
- img - img
- text: "- src/App.tsx - src/main.tsx - src/vite-env.d.ts (3 files total)" - text: ""
\ No newline at end of file \ No newline at end of file
- 'button "List Files: src (recursive)"': - button "src recursive - src/App.tsx - src/main.tsx - src/vite-env.d.ts (3 files total)" [expanded]:
- img - img
- text: ""
- img - img
- text: "- src/App.tsx - src/main.tsx - src/vite-env.d.ts (3 files total)" - text: ""
\ No newline at end of file \ No newline at end of file
- button "List Files (recursive) (include hidden)": - button /List Files recursive include hidden - \.dyad\/plans\/test-plan\.md - index\.html - package\.json - src\/App\.tsx - src\/main\.tsx - src\/vite-env\.d\.ts - tsconfig\.app\.json - tsconfig\.json - tsconfig\.node\.json - vite\.config\.ts \(\d+ files total\)/ [expanded]:
- img - img
- text: ""
- img - img
- text: /- \.dyad\/plans\/test-plan\.md - index\.html - package\.json - src\/App\.tsx - src\/main\.tsx - src\/vite-env\.d\.ts - tsconfig\.app\.json - tsconfig\.json - tsconfig\.node\.json - vite\.config\.ts \(\d+ files total\)/ - text: ""
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit":
- text: file1.txt - img
- button "Edit": - text: ""
- button "Edit":
- img
- text: ""
- img - img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button "Copy": - button "Copy":
- img - img
...@@ -16,19 +17,23 @@ ...@@ -16,19 +17,23 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- paragraph: tc=local-agent/read-logs - paragraph: tc=local-agent/read-logs
- paragraph: Let me check the recent console logs to see what's happening in the application. - paragraph: Let me check the recent console logs to see what's happening in the application.
- img - button "LOGS Reading 1 logs":
- text: /LOGSReading \d+ logs/ - img
- img - text: ""
- img
- paragraph: Now let me filter for only error logs to identify any issues. - paragraph: Now let me filter for only error logs to identify any issues.
- img - 'button "LOGS Reading 0 logs (level: error)"':
- text: "LOGSReading 0 logs (level: error)" - img
- img - text: ""
- img
- paragraph: Let me also check client-side logs specifically. - paragraph: Let me also check client-side logs specifically.
- img - 'button "LOGS Reading 0 logs (type: client)"':
- text: "LOGSReading 0 logs (type: client)" - img
- img - text: ""
- img
- paragraph: I've reviewed the console logs. The application appears to be running normally with no critical errors detected. - paragraph: I've reviewed the console logs. The application appears to be running normally with no critical errors detected.
- button "Copy": - button "Copy":
- img - img
...@@ -38,7 +43,10 @@ ...@@ -38,7 +43,10 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit":
- text: file1.txt - img
- button "Edit": - text: ""
- button "Edit":
- img
- text: ""
- img - img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button "Copy": - button "Copy":
- img - img
...@@ -16,15 +17,17 @@ ...@@ -16,15 +17,17 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- paragraph: tc=local-agent/search-replace - paragraph: tc=local-agent/search-replace
- paragraph: Let me first read the file to see its contents. - paragraph: Let me first read the file to see its contents.
- img - img
- text: App.tsx Read src/App.tsx - text: Read src/App.tsx
- paragraph: Now I'll use search_replace to update the text with proper context. - paragraph: Now I'll use search_replace to update the text with proper context.
- img - button "Search & Replace App.tsx src/App.tsx":
- text: Search & Replace App.tsx - img
- img - text: ""
- text: src/App.tsx - img
- text: ""
- paragraph: Done! I've updated the message using search_replace. The edit was applied successfully. - paragraph: Done! I've updated the message using search_replace. The edit was applied successfully.
- button "Copy": - button "Copy":
- img - img
...@@ -34,7 +37,10 @@ ...@@ -34,7 +37,10 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: Summarize from chat-id=1 - paragraph: Summarize from chat-id=1
- img - button "file1.txt file1.txt Edit"
- text: file1.txt
- button "Edit":
- img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button "Copy": - button "Copy":
- img - img
...@@ -17,4 +12,4 @@ ...@@ -17,4 +12,4 @@
- button "Undo": - button "Undo":
- img - img
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
- paragraph: hi - paragraph: hi
- img - button "file1.txt file1.txt Edit"
- text: file1.txt
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
- paragraph: "[call_tool=calculator_add]" - paragraph: "[call_tool=calculator_add]"
- img - button "Tool Call testing-mcp-server calculator_add"
- text: Tool Call
- img
- text: testing-mcp-server calculator_add
- img - img
- text: less than a minute ago - text: less than a minute ago
- paragraph: "[call_tool=calculator_add]" - paragraph: "[call_tool=calculator_add]"
- img - button "Tool Call testing-mcp-server calculator_add":
- text: Tool Call - img
- img - text: ""
- text: testing-mcp-server calculator_add - img
- img - img
- text: less than a minute ago - text: less than a minute ago
\ No newline at end of file
- paragraph: hi - paragraph: hi
- button "file1.txt file1.txt Edit":
- img
- text: ""
- button "Edit":
- img
- text: ""
- img
- paragraph: More EOM
- button "Copy":
- img
- img - img
- text: file1.txt - text: testollama
- img - img
- text: file1.txt - text: less than a minute ago
- paragraph: More EOM - button "Undo":
- img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ - paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
- img - button "file1.txt file1.txt Edit":
- text: file1.txt - img
- img - text: ""
- text: file1.txt - button "Edit":
- img
- text: ""
- img
- paragraph: More EOM - paragraph: More EOM
- button "Copy":
- img
- img - img
- text: Approved - text: Approved
- paragraph: tc=partial-write
- paragraph: START OF MESSAGE
- img - img
- text: new-file.ts - text: test-model
- img - img
- text: "src/new-file.ts Summary: this file will be partially written" - text: less than a minute ago
- img
- text: wrote 1 file(s)
- paragraph: tc=partial-write
- paragraph: START OF MESSAGE
- 'button "new-file.ts src/new-file.ts Edit Summary: this file will be partially written"':
- img
- text: ""
- button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: Finished writing file.
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- button "Copy":
- img
- img - img
- text: Approved - text: Approved
- img
- text: test-model
- img
- text: less than a minute ago
- img
- text: wrote 1 file(s)
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
...@@ -15,13 +15,7 @@ ...@@ -15,13 +15,7 @@
- paragraph: - paragraph:
- strong: Which framework do you prefer? - strong: Which framework do you prefer?
- text: Vue - text: Vue
- img - button "file1.txt file1.txt Edit"
- text: file1.txt
- button "Edit":
- img
- text: Edit
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button "Copy": - button "Copy":
- img - img
......
- paragraph: tc=create-ts-errors - paragraph: tc=create-ts-errors
- paragraph: This will get a TypeScript error. - paragraph: This will get a TypeScript error.
- img - 'button "bad-file.ts src/bad-file.ts Edit Summary: This will get a TypeScript error."':
- text: bad-file.ts - img
- img - text: ""
- text: "src/bad-file.ts Summary: This will get a TypeScript error." - button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: EOM - paragraph: EOM
- img - button "Auto-fix 5 problems":
- text: Auto-fix5 problems - img
- img - text: ""
- img - img
- text: file1.txt - button "file1.txt file1.txt Edit":
- img - img
- text: file1.txt - text: ""
- button "Edit":
- img
- text: ""
- img
- paragraph: More EOM - paragraph: More EOM
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- img - button "Auto-fix 5 problems":
- text: Auto-fix5 problems - img
- img - text: ""
- img - img
- text: file1.txt - button "file1.txt file1.txt Edit":
- img - img
- text: file1.txt - text: ""
- button "Edit":
- img
- text: ""
- img
- paragraph: More EOM - paragraph: More EOM
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- button "Copy":
- img
- img
- text: test-model
- img
- text: less than a minute ago
- paragraph: "Fix these 3 TypeScript compile-time errors:" - paragraph: "Fix these 3 TypeScript compile-time errors:"
- list: - list:
- listitem: src/bad-file.tsx:2:1 - Cannot find name 'nonExistentFunction1'. (TS2304) - listitem: src/bad-file.tsx:2:1 - Cannot find name 'nonExistentFunction1'. (TS2304)
- text: plaintext
- code: const App = () => <div>Minimal imported app</div>; nonExistentFunction1(); // <-- TypeScript compiler error here nonExistentFunction2(); - code: const App = () => <div>Minimal imported app</div>; nonExistentFunction1(); // <-- TypeScript compiler error here nonExistentFunction2();
- list: - list:
- listitem: src/bad-file.tsx:3:1 - Cannot find name 'nonExistentFunction2'. (TS2304) - listitem: src/bad-file.tsx:3:1 - Cannot find name 'nonExistentFunction2'. (TS2304)
- text: plaintext
- code: nonExistentFunction1(); nonExistentFunction2(); // <-- TypeScript compiler error here nonExistentFunction3(); - code: nonExistentFunction1(); nonExistentFunction2(); // <-- TypeScript compiler error here nonExistentFunction3();
- list: - list:
- listitem: src/bad-file.tsx:4:1 - Cannot find name 'nonExistentFunction3'. (TS2304) - listitem: src/bad-file.tsx:4:1 - Cannot find name 'nonExistentFunction3'. (TS2304)
- text: plaintext
- code: nonExistentFunction2(); nonExistentFunction3(); // <-- TypeScript compiler error here - code: nonExistentFunction2(); nonExistentFunction3(); // <-- TypeScript compiler error here
- paragraph: Please fix all errors in a concise way. - paragraph: Please fix all errors in a concise way.
- img - button "file1.txt file1.txt Edit":
- text: file1.txt - img
- img - text: ""
- text: file1.txt - button "Edit":
- img
- text: ""
- img
- paragraph: More EOM - paragraph: More EOM
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- img - button "Auto-fix 3 problems":
- text: Auto-fix3 problems - img
- img - text: ""
- img - img
- text: file1.txt - button "file1.txt file1.txt Edit":
- img - img
- text: file1.txt - text: ""
- button "Edit":
- img
- text: ""
- img
- paragraph: More EOM - paragraph: More EOM
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- img - button "Auto-fix 3 problems":
- text: Auto-fix3 problems - img
- img - text: ""
- img - img
- text: file1.txt - button "file1.txt file1.txt Edit":
- img - img
- text: file1.txt - text: ""
- button "Edit":
- img
- text: ""
- img
- paragraph: More EOM - paragraph: More EOM
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- button "Copy":
- img
- img
- text: test-model
- img
- text: less than a minute ago
- button "Undo":
- img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: "Fix these 2 TypeScript compile-time errors:" - paragraph: "Fix these 2 TypeScript compile-time errors:"
- list: - list:
- listitem: src/bad-file.tsx:2:1 - Cannot find name 'nonExistentFunction1'. (TS2304) - listitem: src/bad-file.tsx:2:1 - Cannot find name 'nonExistentFunction1'. (TS2304)
- text: plaintext
- code: const App = () => <div>Minimal imported app</div>; nonExistentFunction1(); // <-- TypeScript compiler error here nonExistentFunction2(); - code: const App = () => <div>Minimal imported app</div>; nonExistentFunction1(); // <-- TypeScript compiler error here nonExistentFunction2();
- list: - list:
- listitem: src/bad-file.tsx:4:1 - Cannot find name 'nonExistentFunction3'. (TS2304) - listitem: src/bad-file.tsx:4:1 - Cannot find name 'nonExistentFunction3'. (TS2304)
- text: plaintext
- code: nonExistentFunction2(); nonExistentFunction3(); // <-- TypeScript compiler error here - code: nonExistentFunction2(); nonExistentFunction3(); // <-- TypeScript compiler error here
- paragraph: Please fix all errors in a concise way. - paragraph: Please fix all errors in a concise way.
- img - 'button "bad-file.ts src/bad-file.ts Edit Summary: Fix 2 errors and introduce a new error."':
- text: bad-file.ts
- button "Edit":
- img - img
- img - text: ""
- text: "src/bad-file.ts Summary: Fix 2 errors and introduce a new error." - button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- button: - button "Copy":
- img - img
- img - img
- text: test-model
- img
- text: less than a minute ago - text: less than a minute ago
- button "Undo":
- img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
...@@ -4,26 +4,50 @@ ...@@ -4,26 +4,50 @@
- text: main.tsx Delete src/main.tsx - text: main.tsx Delete src/main.tsx
- img - img
- text: "App.tsx main.tsx Rename From: src/App.tsx To: src/main.tsx" - text: "App.tsx main.tsx Rename From: src/App.tsx To: src/main.tsx"
- img - 'button "main.tsx src/main.tsx Edit Summary: final main.tsx file."':
- text: main.tsx - img
- img - text: ""
- text: "src/main.tsx Summary: final main.tsx file." - button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: EOM - paragraph: EOM
- img - button "Auto-fix 1 problems":
- text: Auto-fix1 problems - img
- img - text: ""
- img - img
- text: bad-file.ts - 'button "bad-file.ts src/bad-file.ts Edit Summary: Fix remaining error."':
- img - img
- text: "src/bad-file.ts Summary: Fix remaining error." - text: ""
- button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- button "Auto-fix 1 problems":
- img
- text: ""
- img
- 'button "bad-file.ts src/bad-file.ts Edit Summary: Fix remaining error."':
- img
- text: ""
- button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: "[[dyad-dump-path=*]]"
- button "Copy":
- img
- img - img
- text: Auto-fix1 problems - text: test-model
- img
- img
- text: bad-file.ts
- img - img
- text: "src/bad-file.ts Summary: Fix remaining error." - text: less than a minute ago
- paragraph: "[[dyad-dump-path=*]]" - button "Undo":
- img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: tc=create-ts-errors - paragraph: tc=create-ts-errors
- paragraph: This will get a TypeScript error. - paragraph: This will get a TypeScript error.
- 'button "bad-file.ts src/bad-file.ts Edit Summary: This will get a TypeScript error."':
- img
- text: ""
- button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: EOM
- button "Copy":
- img
- img - img
- text: bad-file.ts - text: test-model
- img - img
- text: "src/bad-file.ts Summary: This will get a TypeScript error." - text: less than a minute ago
- paragraph: EOM - button "Undo":
- img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: tc=create-ts-errors - paragraph: tc=create-ts-errors
- paragraph: This will get a TypeScript error. - paragraph: This will get a TypeScript error.
- img - 'button "bad-file.ts src/bad-file.ts Edit Summary: This will get a TypeScript error."':
- text: bad-file.ts - img
- img - text: ""
- text: "src/bad-file.ts Summary: This will get a TypeScript error." - button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: EOM - paragraph: EOM
- img - button "Auto-fix 2 problems":
- text: Auto-fix2 problems - img
- img - text: ""
- img - img
- text: bad-file.ts - 'button "bad-file.ts src/bad-file.ts Edit Summary: Fix 2 errors and introduce a new error."':
- img - img
- text: "src/bad-file.ts Summary: Fix 2 errors and introduce a new error." - text: ""
- button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- button "Auto-fix 1 problems":
- img
- text: ""
- img
- 'button "bad-file.ts src/bad-file.ts Edit Summary: Fix remaining error."':
- img
- text: ""
- button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: "[[dyad-dump-path=*]]"
- button "Copy":
- img
- img - img
- text: Auto-fix1 problems - text: test-model
- img
- img
- text: bad-file.ts
- img - img
- text: "src/bad-file.ts Summary: Fix remaining error." - text: less than a minute ago
- paragraph: "[[dyad-dump-path=*]]" - button "Undo":
- img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- img - button /Auto-fix 5 problems 1 src\/bad-file\.ts 1:\d+ TS2307 Cannot find module 'non-existent-class' or its corresponding type declarations\. 2 src\/bad-file\.ts 2:\d+ TS2307 Cannot find module 'non-existent-class' or its corresponding type declarations\. 3 src\/bad-file\.ts 3:\d+ TS2307 Cannot find module 'non-existent-class' or its corresponding type declarations\. 4 src\/bad-file\.ts 4:\d+ TS2307 Cannot find module 'non-existent-class' or its corresponding type declarations\. 5 src\/bad-file\.ts 5:\d+ TS2307 Cannot find module 'non-existent-class' or its corresponding type declarations\./ [expanded]:
- text: Auto-fix5 problems - img
- img - text: ""
- text: "1" - img
- img - text: ""
- text: /src\/bad-file\.ts 1:\d+ TS2307/ - img
- paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations. - text: ""
- text: "2" - paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations.
- img - text: ""
- text: /src\/bad-file\.ts 2:\d+ TS2307/ - img
- paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations. - text: ""
- text: "3" - paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations.
- img - text: ""
- text: /src\/bad-file\.ts 3:\d+ TS2307/ - img
- paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations. - text: ""
- text: "4" - paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations.
- img - text: ""
- text: /src\/bad-file\.ts 4:\d+ TS2307/ - img
- paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations. - text: ""
- text: "5" - paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations.
- img - text: ""
- text: /src\/bad-file\.ts 5:\d+ TS2307/ - img
- paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations. - text: ""
\ No newline at end of file - paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations.
\ No newline at end of file
- paragraph: tc=create-unfixable-ts-errors - paragraph: tc=create-unfixable-ts-errors
- paragraph: This should not get fixed - paragraph: This should not get fixed
- img - 'button "bad-file.ts src/bad-file.ts Edit Summary: This will produce 5 TypeScript errors."':
- text: bad-file.ts - img
- img - text: ""
- text: "src/bad-file.ts Summary: This will produce 5 TypeScript errors." - button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: EOM - paragraph: EOM
- img - button "Auto-fix 5 problems":
- text: Auto-fix5 problems - img
- img - text: ""
- img - img
- text: file1.txt - button "file1.txt file1.txt Edit":
- img - img
- text: file1.txt - text: ""
- button "Edit":
- img
- text: ""
- img
- paragraph: More EOM - paragraph: More EOM
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- img - button /Auto-fix 5 problems 1 src\/bad-file\.ts 1:\d+ TS2307 Cannot find module 'non-existent-class' or its corresponding type declarations\. 2 src\/bad-file\.ts 2:\d+ TS2307 Cannot find module 'non-existent-class' or its corresponding type declarations\. 3 src\/bad-file\.ts 3:\d+ TS2307 Cannot find module 'non-existent-class' or its corresponding type declarations\. 4 src\/bad-file\.ts 4:\d+ TS2307 Cannot find module 'non-existent-class' or its corresponding type declarations\. 5 src\/bad-file\.ts 5:\d+ TS2307 Cannot find module 'non-existent-class' or its corresponding type declarations\./ [expanded]:
- text: Auto-fix5 problems - img
- img - text: ""
- text: "1" - img
- img - text: ""
- text: /src\/bad-file\.ts 1:\d+ TS2307/ - img
- paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations. - text: ""
- text: "2" - paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations.
- img - text: ""
- text: /src\/bad-file\.ts 2:\d+ TS2307/ - img
- paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations. - text: ""
- text: "3" - paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations.
- img - text: ""
- text: /src\/bad-file\.ts 3:\d+ TS2307/ - img
- paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations. - text: ""
- text: "4" - paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations.
- img - text: ""
- text: /src\/bad-file\.ts 4:\d+ TS2307/ - img
- paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations. - text: ""
- text: "5" - paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations.
- img - text: ""
- text: /src\/bad-file\.ts 5:\d+ TS2307/ - img
- paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations. - text: ""
- img - paragraph: Cannot find module 'non-existent-class' or its corresponding type declarations.
- text: file1.txt - button "file1.txt file1.txt Edit":
- img - img
- text: file1.txt - text: ""
- button "Edit":
- img
- text: ""
- img
- paragraph: More EOM - paragraph: More EOM
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- button "Copy":
- img
- img
- text: test-model
- img
- text: less than a minute ago
- button "Undo":
- img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: tc=write-index - paragraph: tc=write-index
- paragraph: OK, I'm going to do some writing now... - paragraph: OK, I'm going to do some writing now...
- 'button "Index.tsx src/pages/Index.tsx Edit Summary: write-description"':
- img
- text: ""
- button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: And it's done!
- button "Copy":
- img
- img - img
- text: Index.tsx - text: test-model
- img - img
- text: "src/pages/Index.tsx Summary: write-description" - text: less than a minute ago
- paragraph: And it's done! - button "Undo":
- img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: tc=write-index - paragraph: tc=write-index
- paragraph: OK, I'm going to do some writing now... - paragraph: OK, I'm going to do some writing now...
- img - 'button "Index.tsx src/pages/Index.tsx Edit Summary: write-description"':
- text: Index.tsx - img
- img - text: ""
- text: "src/pages/Index.tsx Summary: write-description" - button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: And it's done! - paragraph: And it's done!
- button "Copy":
- img
- img - img
- text: Rejected - text: Rejected
- img
- text: test-model
- img
- text: less than a minute ago
- button "Undo":
- img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
...@@ -53,12 +53,7 @@ ...@@ -53,12 +53,7 @@
- code: "`src/config/aws.ts`" - code: "`src/config/aws.ts`"
- text: "," - text: ","
- code: "`src/services/s3-uploader.ts`" - code: "`src/services/s3-uploader.ts`"
- img - button "file1.txt file1.txt Edit"
- text: file1.txt
- button "Edit":
- img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button: - button:
- img - img
...@@ -73,4 +68,4 @@ ...@@ -73,4 +68,4 @@
- button "Undo": - button "Undo":
- img - img
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
- table: - table:
- rowgroup: - rowgroup:
- row "Select all issues Level Issue Action": - row "Select all issues Level Issue Action":
- cell "Select all issues": - columnheader "Select all issues":
- checkbox "Select all issues" - checkbox "Select all issues"
- cell "Level" - columnheader "Level"
- cell "Issue" - columnheader "Issue"
- cell "Action" - columnheader "Action"
- rowgroup: - rowgroup:
- 'row "Select SQL Injection in User Lookup critical SQL Injection in User Lookup What: User input flows directly into database queries without validation, allowing attackers to execute arbitrary SQL commands Risk: An attac... Show more Fix Issue"': - 'row "Select SQL Injection in User Lookup critical SQL Injection in User Lookup What: User input flows directly into database queries without validation, allowing attackers to execute arbitrary SQL commands Risk: An attac... Show more Fix Issue"':
- cell "Select SQL Injection in User Lookup": - cell "Select SQL Injection in User Lookup":
- checkbox "Select SQL Injection in User Lookup" - checkbox "Select SQL Injection in User Lookup"
- cell "critical": - cell "critical":
- img - img
- text: ""
- 'cell "SQL Injection in User Lookup What: User input flows directly into database queries without validation, allowing attackers to execute arbitrary SQL commands Risk: An attac... Show more"': - 'cell "SQL Injection in User Lookup What: User input flows directly into database queries without validation, allowing attackers to execute arbitrary SQL commands Risk: An attac... Show more"':
- 'button "SQL Injection in User Lookup What: User input flows directly into database queries without validation, allowing attackers to execute arbitrary SQL commands Risk: An attac... Show more"': - 'button "SQL Injection in User Lookup What: User input flows directly into database queries without validation, allowing attackers to execute arbitrary SQL commands Risk: An attac... Show more"':
- text: ""
- paragraph: - paragraph:
- strong: What - strong: What
- text: ": User input flows directly into database queries without validation, allowing attackers to execute arbitrary SQL commands" - text: ": User input flows directly into database queries without validation, allowing attackers to execute arbitrary SQL commands"
...@@ -22,6 +24,7 @@ ...@@ -22,6 +24,7 @@
- text: ": An attac..." - text: ": An attac..."
- button "Show more": - button "Show more":
- img - img
- text: ""
- cell "Fix Issue": - cell "Fix Issue":
- button "Fix Issue" - button "Fix Issue"
- 'row "Select Hardcoded AWS Credentials in Source Code critical Hardcoded AWS Credentials in Source Code What: AWS access keys are stored directly in the codebase and committed to version control, exposing full cloud infrastructure access Risk: A... Show more Fix Issue"': - 'row "Select Hardcoded AWS Credentials in Source Code critical Hardcoded AWS Credentials in Source Code What: AWS access keys are stored directly in the codebase and committed to version control, exposing full cloud infrastructure access Risk: A... Show more Fix Issue"':
...@@ -29,8 +32,10 @@ ...@@ -29,8 +32,10 @@
- checkbox "Select Hardcoded AWS Credentials in Source Code" - checkbox "Select Hardcoded AWS Credentials in Source Code"
- cell "critical": - cell "critical":
- img - img
- text: ""
- 'cell "Hardcoded AWS Credentials in Source Code What: AWS access keys are stored directly in the codebase and committed to version control, exposing full cloud infrastructure access Risk: A... Show more"': - 'cell "Hardcoded AWS Credentials in Source Code What: AWS access keys are stored directly in the codebase and committed to version control, exposing full cloud infrastructure access Risk: A... Show more"':
- 'button "Hardcoded AWS Credentials in Source Code What: AWS access keys are stored directly in the codebase and committed to version control, exposing full cloud infrastructure access Risk: A... Show more"': - 'button "Hardcoded AWS Credentials in Source Code What: AWS access keys are stored directly in the codebase and committed to version control, exposing full cloud infrastructure access Risk: A... Show more"':
- text: ""
- paragraph: - paragraph:
- strong: What - strong: What
- text: ": AWS access keys are stored directly in the codebase and committed to version control, exposing full cloud infrastructure access" - text: ": AWS access keys are stored directly in the codebase and committed to version control, exposing full cloud infrastructure access"
...@@ -39,6 +44,7 @@ ...@@ -39,6 +44,7 @@
- text: ": A..." - text: ": A..."
- button "Show more": - button "Show more":
- img - img
- text: ""
- cell "Fix Issue": - cell "Fix Issue":
- button "Fix Issue" - button "Fix Issue"
- 'row "Select Missing Authentication on Admin Endpoints high Missing Authentication on Admin Endpoints What: Administrative API endpoints can be accessed without authentication, relying only on URL obscurity Risk: An attacker who discovers thes... Show more Fix Issue"': - 'row "Select Missing Authentication on Admin Endpoints high Missing Authentication on Admin Endpoints What: Administrative API endpoints can be accessed without authentication, relying only on URL obscurity Risk: An attacker who discovers thes... Show more Fix Issue"':
...@@ -46,8 +52,10 @@ ...@@ -46,8 +52,10 @@
- checkbox "Select Missing Authentication on Admin Endpoints" - checkbox "Select Missing Authentication on Admin Endpoints"
- cell "high": - cell "high":
- img - img
- text: ""
- 'cell "Missing Authentication on Admin Endpoints What: Administrative API endpoints can be accessed without authentication, relying only on URL obscurity Risk: An attacker who discovers thes... Show more"': - 'cell "Missing Authentication on Admin Endpoints What: Administrative API endpoints can be accessed without authentication, relying only on URL obscurity Risk: An attacker who discovers thes... Show more"':
- 'button "Missing Authentication on Admin Endpoints What: Administrative API endpoints can be accessed without authentication, relying only on URL obscurity Risk: An attacker who discovers thes... Show more"': - 'button "Missing Authentication on Admin Endpoints What: Administrative API endpoints can be accessed without authentication, relying only on URL obscurity Risk: An attacker who discovers thes... Show more"':
- text: ""
- paragraph: - paragraph:
- strong: What - strong: What
- text: ": Administrative API endpoints can be accessed without authentication, relying only on URL obscurity" - text: ": Administrative API endpoints can be accessed without authentication, relying only on URL obscurity"
...@@ -56,6 +64,7 @@ ...@@ -56,6 +64,7 @@
- text: ": An attacker who discovers thes..." - text: ": An attacker who discovers thes..."
- button "Show more": - button "Show more":
- img - img
- text: ""
- cell "Fix Issue": - cell "Fix Issue":
- button "Fix Issue" - button "Fix Issue"
- 'row "Select JWT Secret Using Default Value high JWT Secret Using Default Value What: The application uses a hardcoded default JWT secret (\"your-secret-key\") for signing authentication tokens Risk: Attackers can forge val... Show more Fix Issue"': - 'row "Select JWT Secret Using Default Value high JWT Secret Using Default Value What: The application uses a hardcoded default JWT secret (\"your-secret-key\") for signing authentication tokens Risk: Attackers can forge val... Show more Fix Issue"':
...@@ -63,8 +72,10 @@ ...@@ -63,8 +72,10 @@
- checkbox "Select JWT Secret Using Default Value" - checkbox "Select JWT Secret Using Default Value"
- cell "high": - cell "high":
- img - img
- text: ""
- 'cell "JWT Secret Using Default Value What: The application uses a hardcoded default JWT secret (\"your-secret-key\") for signing authentication tokens Risk: Attackers can forge val... Show more"': - 'cell "JWT Secret Using Default Value What: The application uses a hardcoded default JWT secret (\"your-secret-key\") for signing authentication tokens Risk: Attackers can forge val... Show more"':
- 'button "JWT Secret Using Default Value What: The application uses a hardcoded default JWT secret (\"your-secret-key\") for signing authentication tokens Risk: Attackers can forge val... Show more"': - 'button "JWT Secret Using Default Value What: The application uses a hardcoded default JWT secret (\"your-secret-key\") for signing authentication tokens Risk: Attackers can forge val... Show more"':
- text: ""
- paragraph: - paragraph:
- strong: What - strong: What
- text: ": The application uses a hardcoded default JWT secret (\"your-secret-key\") for signing authentication tokens" - text: ": The application uses a hardcoded default JWT secret (\"your-secret-key\") for signing authentication tokens"
...@@ -73,6 +84,7 @@ ...@@ -73,6 +84,7 @@
- text: ": Attackers can forge val..." - text: ": Attackers can forge val..."
- button "Show more": - button "Show more":
- img - img
- text: ""
- cell "Fix Issue": - cell "Fix Issue":
- button "Fix Issue" - button "Fix Issue"
- 'row "Select Unvalidated File Upload Extensions medium Unvalidated File Upload Extensions What: The file upload endpoint accepts any file type without validating extensions or content, only checking file size Risk: An attacker coul... Show more Fix Issue"': - 'row "Select Unvalidated File Upload Extensions medium Unvalidated File Upload Extensions What: The file upload endpoint accepts any file type without validating extensions or content, only checking file size Risk: An attacker coul... Show more Fix Issue"':
...@@ -80,8 +92,10 @@ ...@@ -80,8 +92,10 @@
- checkbox "Select Unvalidated File Upload Extensions" - checkbox "Select Unvalidated File Upload Extensions"
- cell "medium": - cell "medium":
- img - img
- text: ""
- 'cell "Unvalidated File Upload Extensions What: The file upload endpoint accepts any file type without validating extensions or content, only checking file size Risk: An attacker coul... Show more"': - 'cell "Unvalidated File Upload Extensions What: The file upload endpoint accepts any file type without validating extensions or content, only checking file size Risk: An attacker coul... Show more"':
- 'button "Unvalidated File Upload Extensions What: The file upload endpoint accepts any file type without validating extensions or content, only checking file size Risk: An attacker coul... Show more"': - 'button "Unvalidated File Upload Extensions What: The file upload endpoint accepts any file type without validating extensions or content, only checking file size Risk: An attacker coul... Show more"':
- text: ""
- paragraph: - paragraph:
- strong: What - strong: What
- text: ": The file upload endpoint accepts any file type without validating extensions or content, only checking file size" - text: ": The file upload endpoint accepts any file type without validating extensions or content, only checking file size"
...@@ -90,6 +104,7 @@ ...@@ -90,6 +104,7 @@
- text: ": An attacker coul..." - text: ": An attacker coul..."
- button "Show more": - button "Show more":
- img - img
- text: ""
- cell "Fix Issue": - cell "Fix Issue":
- button "Fix Issue" - button "Fix Issue"
- 'row "Select Missing CSRF Protection on State-Changing Operations medium Missing CSRF Protection on State-Changing Operations What: POST, PUT, and DELETE endpoints don''t implement CSRF tokens, making them vulnerable to cross-site request forgery attacks Risk: An atta... Show more Fix Issue"': - 'row "Select Missing CSRF Protection on State-Changing Operations medium Missing CSRF Protection on State-Changing Operations What: POST, PUT, and DELETE endpoints don''t implement CSRF tokens, making them vulnerable to cross-site request forgery attacks Risk: An atta... Show more Fix Issue"':
...@@ -97,8 +112,10 @@ ...@@ -97,8 +112,10 @@
- checkbox "Select Missing CSRF Protection on State-Changing Operations" - checkbox "Select Missing CSRF Protection on State-Changing Operations"
- cell "medium": - cell "medium":
- img - img
- text: ""
- 'cell "Missing CSRF Protection on State-Changing Operations What: POST, PUT, and DELETE endpoints don''t implement CSRF tokens, making them vulnerable to cross-site request forgery attacks Risk: An atta... Show more"': - 'cell "Missing CSRF Protection on State-Changing Operations What: POST, PUT, and DELETE endpoints don''t implement CSRF tokens, making them vulnerable to cross-site request forgery attacks Risk: An atta... Show more"':
- 'button "Missing CSRF Protection on State-Changing Operations What: POST, PUT, and DELETE endpoints don''t implement CSRF tokens, making them vulnerable to cross-site request forgery attacks Risk: An atta... Show more"': - 'button "Missing CSRF Protection on State-Changing Operations What: POST, PUT, and DELETE endpoints don''t implement CSRF tokens, making them vulnerable to cross-site request forgery attacks Risk: An atta... Show more"':
- text: ""
- paragraph: - paragraph:
- strong: What - strong: What
- text: ": POST, PUT, and DELETE endpoints don't implement CSRF tokens, making them vulnerable to cross-site request forgery attacks" - text: ": POST, PUT, and DELETE endpoints don't implement CSRF tokens, making them vulnerable to cross-site request forgery attacks"
...@@ -107,6 +124,7 @@ ...@@ -107,6 +124,7 @@
- text: ": An atta..." - text: ": An atta..."
- button "Show more": - button "Show more":
- img - img
- text: ""
- cell "Fix Issue": - cell "Fix Issue":
- button "Fix Issue" - button "Fix Issue"
- 'row "Select Verbose Error Messages Expose Stack Traces low Verbose Error Messages Expose Stack Traces What: Production error responses include full stack traces and internal file paths that are sent to end users Risk: Attackers can use this in... Show more Fix Issue"': - 'row "Select Verbose Error Messages Expose Stack Traces low Verbose Error Messages Expose Stack Traces What: Production error responses include full stack traces and internal file paths that are sent to end users Risk: Attackers can use this in... Show more Fix Issue"':
...@@ -114,8 +132,10 @@ ...@@ -114,8 +132,10 @@
- checkbox "Select Verbose Error Messages Expose Stack Traces" - checkbox "Select Verbose Error Messages Expose Stack Traces"
- cell "low": - cell "low":
- img - img
- text: ""
- 'cell "Verbose Error Messages Expose Stack Traces What: Production error responses include full stack traces and internal file paths that are sent to end users Risk: Attackers can use this in... Show more"': - 'cell "Verbose Error Messages Expose Stack Traces What: Production error responses include full stack traces and internal file paths that are sent to end users Risk: Attackers can use this in... Show more"':
- 'button "Verbose Error Messages Expose Stack Traces What: Production error responses include full stack traces and internal file paths that are sent to end users Risk: Attackers can use this in... Show more"': - 'button "Verbose Error Messages Expose Stack Traces What: Production error responses include full stack traces and internal file paths that are sent to end users Risk: Attackers can use this in... Show more"':
- text: ""
- paragraph: - paragraph:
- strong: What - strong: What
- text: ": Production error responses include full stack traces and internal file paths that are sent to end users" - text: ": Production error responses include full stack traces and internal file paths that are sent to end users"
...@@ -124,6 +144,7 @@ ...@@ -124,6 +144,7 @@
- text: ": Attackers can use this in..." - text: ": Attackers can use this in..."
- button "Show more": - button "Show more":
- img - img
- text: ""
- cell "Fix Issue": - cell "Fix Issue":
- button "Fix Issue" - button "Fix Issue"
- 'row "Select Missing Security Headers low Missing Security Headers What: The application doesn''t set recommended security headers like `X-Frame-Options`, `X-Content-Type-Options`, and `Strict-Transport-Security` ... Show more Fix Issue"': - 'row "Select Missing Security Headers low Missing Security Headers What: The application doesn''t set recommended security headers like `X-Frame-Options`, `X-Content-Type-Options`, and `Strict-Transport-Security` ... Show more Fix Issue"':
...@@ -131,8 +152,10 @@ ...@@ -131,8 +152,10 @@
- checkbox "Select Missing Security Headers" - checkbox "Select Missing Security Headers"
- cell "low": - cell "low":
- img - img
- text: ""
- 'cell "Missing Security Headers What: The application doesn''t set recommended security headers like `X-Frame-Options`, `X-Content-Type-Options`, and `Strict-Transport-Security` ... Show more"': - 'cell "Missing Security Headers What: The application doesn''t set recommended security headers like `X-Frame-Options`, `X-Content-Type-Options`, and `Strict-Transport-Security` ... Show more"':
- 'button "Missing Security Headers What: The application doesn''t set recommended security headers like `X-Frame-Options`, `X-Content-Type-Options`, and `Strict-Transport-Security` ... Show more"': - 'button "Missing Security Headers What: The application doesn''t set recommended security headers like `X-Frame-Options`, `X-Content-Type-Options`, and `Strict-Transport-Security` ... Show more"':
- text: ""
- paragraph: - paragraph:
- strong: What - strong: What
- text: ": The application doesn't set recommended security headers like" - text: ": The application doesn't set recommended security headers like"
...@@ -144,5 +167,6 @@ ...@@ -144,5 +167,6 @@
- paragraph: ... - paragraph: ...
- button "Show more": - button "Show more":
- img - img
- text: ""
- cell "Fix Issue": - cell "Fix Issue":
- button "Fix Issue" - button "Fix Issue"
\ No newline at end of file
...@@ -24,12 +24,7 @@ ...@@ -24,12 +24,7 @@
- strong: Relevant Files - strong: Relevant Files
- text: ":" - text: ":"
- code: "`src/api/users.ts`" - code: "`src/api/users.ts`"
- img - button "file1.txt file1.txt Edit"
- text: file1.txt
- button "Edit":
- img
- img
- text: file1.txt
- paragraph: More EOM - paragraph: More EOM
- button: - button:
- img - img
...@@ -44,4 +39,4 @@ ...@@ -44,4 +39,4 @@
- button "Undo": - button "Undo":
- img - img
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
- paragraph: tc=read-index - paragraph: tc=read-index
- paragraph: "Read the index page:" - paragraph: "Read the index page:"
- img - img
- text: Index.tsx Read src/pages/Index.tsx - text: Read src/pages/Index.tsx
- paragraph: Done. - paragraph: Done.
- button "Copy": - button "Copy":
- img - img
...@@ -13,14 +13,17 @@ ...@@ -13,14 +13,17 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- paragraph: tc=update-index-1 - paragraph: tc=update-index-1
- paragraph: First read - paragraph: First read
- img - 'button "Index.tsx src/pages/Index.tsx Edit Summary: replace file"':
- text: Index.tsx
- button "Edit":
- img - img
- img - text: ""
- text: "src/pages/Index.tsx Summary: replace file" - button "Edit":
- img
- text: ""
- img
- text: ""
- button "Copy": - button "Copy":
- img - img
- img - img
...@@ -33,10 +36,11 @@ ...@@ -33,10 +36,11 @@
- text: wrote 1 file(s) - text: wrote 1 file(s)
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- paragraph: tc=read-index - paragraph: tc=read-index
- paragraph: "Read the index page:" - paragraph: "Read the index page:"
- img - img
- text: Index.tsx Read src/pages/Index.tsx - text: Read src/pages/Index.tsx
- paragraph: Done. - paragraph: Done.
- button "Copy": - button "Copy":
- img - img
...@@ -48,6 +52,7 @@ ...@@ -48,6 +52,7 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- paragraph: "[dump]" - paragraph: "[dump]"
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- button "Copy": - button "Copy":
...@@ -60,7 +65,10 @@ ...@@ -60,7 +65,10 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: tc=add-supabase - paragraph: tc=add-supabase
- paragraph: Adding supabase... - paragraph: Adding supabase...
- text: Integrate with supabase? - img
- text: Integration Integrate with supabase?
- button "Set up supabase" - button "Set up supabase"
- button "Copy":
- img
- img
- text: test-model
- img
- text: less than a minute ago
- button "Undo":
- img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: tc=add-supabase - paragraph: tc=add-supabase
- paragraph: Adding supabase... - paragraph: Adding supabase...
- img - img
- text: Supabase integration complete - text: Integration Complete Supabase integration complete
- paragraph: "This app is connected to Supabase project: Fake Supabase Project" - paragraph: "This app is connected to Supabase project: Fake Supabase Project"
- button "Continue" - button "Continue"
- button: - button "Copy":
- img - img
- img - img
- text: test-model - text: test-model
...@@ -12,5 +12,7 @@ ...@@ -12,5 +12,7 @@
- text: less than a minute ago - text: less than a minute ago
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: tc=add-supabase - paragraph: tc=add-supabase
- paragraph: Adding supabase... - paragraph: Adding supabase...
- text: Integrate with supabase? - img
- text: Integration Integrate with supabase?
- button "Set up supabase" - button "Set up supabase"
- button "Copy":
- img
- img
- text: test-model
- img
- text: less than a minute ago
- button "Undo":
- img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
- paragraph: tc=turbo-edits-v2 - paragraph: tc=turbo-edits-v2
- paragraph: Example with turbo edit v2 - paragraph: Example with turbo edit v2
- img - button "Search & Replace Index.tsx src/pages/Index.tsx"
- text: Search & Replace Index.tsx
- img
- text: src/pages/Index.tsx
- paragraph: End of turbo edit - paragraph: End of turbo edit
- button "Copy": - button "Copy":
- img - img
...@@ -16,4 +13,4 @@ ...@@ -16,4 +13,4 @@
- button "Undo": - button "Undo":
- img - img
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file
- paragraph: tc=turbo-edits-v2-trigger-fallback - paragraph: tc=turbo-edits-v2-trigger-fallback
- paragraph: Example with turbo edit v2 - paragraph: Example with turbo edit v2
- img - button "Search & Replace Index.tsx src/pages/Index.tsx":
- text: Search & Replace Index.tsx - img
- img - text: ""
- text: src/pages/Index.tsx - img
- text: ""
- paragraph: End of turbo edit - paragraph: End of turbo edit
- button "Warning Could not apply Turbo Edits properly for some of the files; re-generating code...":
- img
- text: ""
- img
- img - img
- text: Warning Could not apply Turbo Edits properly for some of the files; re-generating code...... - text: Read src/pages/Index.tsx
- img - button "Search & Replace Index.tsx src/pages/Index.tsx":
- img - img
- text: Index.tsx Read src/pages/Index.tsx - text: ""
- img - img
- text: Search & Replace Index.tsx - text: ""
- img
- text: src/pages/Index.tsx
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- img - button "Warning Could not apply Turbo Edits properly for some of the files; re-generating code...":
- text: Warning Could not apply Turbo Edits properly for some of the files; re-generating code......
- img
- img
- text: Index.tsx
- button "Edit":
- img - img
- img - text: ""
- text: "src/pages/Index.tsx Summary: Rewrite file." - img
- 'button "Index.tsx src/pages/Index.tsx Edit Summary: Rewrite file."':
- img
- text: ""
- button "Edit":
- img
- text: ""
- img
- text: ""
- paragraph: "[[dyad-dump-path=*]]" - paragraph: "[[dyad-dump-path=*]]"
- button "Copy": - button "Copy":
- img - img
...@@ -33,7 +39,10 @@ ...@@ -33,7 +39,10 @@
- text: less than a minute ago - text: less than a minute ago
- button "Copy Request ID": - button "Copy Request ID":
- img - img
- text: ""
- button "Undo": - button "Undo":
- img - img
- text: ""
- button "Retry": - button "Retry":
- img - img
\ No newline at end of file - text: ""
\ No newline at end of file
module.exports = { module.exports = {
"**/*.{ts,tsx}": () => "npm run ts", "**/*.{ts,tsx}": () => "npm run ts",
"**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "oxlint", "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "oxlint",
"**/*.{js,css,md,ts,tsx,jsx}": "oxfmt", "**/*.{js,css,md,ts,tsx,jsx,json}": "oxfmt",
}; };
...@@ -100,7 +100,7 @@ ...@@ -100,7 +100,7 @@
"@electron-forge/plugin-vite": "^7.11.1", "@electron-forge/plugin-vite": "^7.11.1",
"@electron-forge/publisher-github": "^7.11.1", "@electron-forge/publisher-github": "^7.11.1",
"@electron/fuses": "^1.8.0", "@electron/fuses": "^1.8.0",
"@playwright/test": "^1.52.0", "@playwright/test": "^1.58.2",
"@storybook/addon-essentials": "^8.6.14", "@storybook/addon-essentials": "^8.6.14",
"@storybook/blocks": "^8.6.14", "@storybook/blocks": "^8.6.14",
"@storybook/react": "^8.6.15", "@storybook/react": "^8.6.15",
...@@ -5428,13 +5428,13 @@ ...@@ -5428,13 +5428,13 @@
} }
}, },
"node_modules/@playwright/test": { "node_modules/@playwright/test": {
"version": "1.55.0", "version": "1.58.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0.tgz", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
"integrity": "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==", "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright": "1.55.0" "playwright": "1.58.2"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
...@@ -19109,13 +19109,13 @@ ...@@ -19109,13 +19109,13 @@
} }
}, },
"node_modules/playwright": { "node_modules/playwright": {
"version": "1.55.0", "version": "1.58.2",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
"integrity": "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==", "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.55.0" "playwright-core": "1.58.2"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
...@@ -19128,9 +19128,9 @@ ...@@ -19128,9 +19128,9 @@
} }
}, },
"node_modules/playwright-core": { "node_modules/playwright-core": {
"version": "1.55.0", "version": "1.58.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
"integrity": "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==", "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
......
...@@ -137,7 +137,7 @@ ...@@ -137,7 +137,7 @@
"@electron-forge/plugin-vite": "^7.11.1", "@electron-forge/plugin-vite": "^7.11.1",
"@electron-forge/publisher-github": "^7.11.1", "@electron-forge/publisher-github": "^7.11.1",
"@electron/fuses": "^1.8.0", "@electron/fuses": "^1.8.0",
"@playwright/test": "^1.52.0", "@playwright/test": "^1.58.2",
"@storybook/addon-essentials": "^8.6.14", "@storybook/addon-essentials": "^8.6.14",
"@storybook/blocks": "^8.6.14", "@storybook/blocks": "^8.6.14",
"@storybook/react": "^8.6.15", "@storybook/react": "^8.6.15",
......
...@@ -100,7 +100,7 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => { ...@@ -100,7 +100,7 @@ const ChatMessage = ({ message, isLastMessage }: ChatMessageProps) => {
<StreamingLoadingAnimation variant="initial" /> <StreamingLoadingAnimation variant="initial" />
) : ( ) : (
<div <div
className="prose dark:prose-invert prose-headings:mb-2 prose-p:my-1 prose-pre:my-0 max-w-none break-words" className="prose dark:prose-invert prose-headings:mb-2 prose-p:my-1 prose-pre:my-0 max-w-none break-words text-[15px]"
suppressHydrationWarning suppressHydrationWarning
> >
{message.role === "assistant" ? ( {message.role === "assistant" ? (
......
...@@ -4,8 +4,15 @@ import { useState } from "react"; ...@@ -4,8 +4,15 @@ import { useState } from "react";
import { ipc } from "@/ipc/types"; import { ipc } from "@/ipc/types";
import { Package, ChevronsUpDown, ChevronsDownUp } from "lucide-react"; import { Package } from "lucide-react";
import { CodeHighlight } from "./CodeHighlight"; import { CodeHighlight } from "./CodeHighlight";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadExpandIcon,
DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadAddDependencyProps { interface DyadAddDependencyProps {
children?: ReactNode; children?: ReactNode;
...@@ -17,77 +24,61 @@ export const DyadAddDependency: React.FC<DyadAddDependencyProps> = ({ ...@@ -17,77 +24,61 @@ export const DyadAddDependency: React.FC<DyadAddDependencyProps> = ({
children, children,
node, node,
}) => { }) => {
// Extract package attribute from the node if available const packages = node?.properties?.packages
const packages = node?.properties?.packages?.split(" ") || ""; ? node.properties.packages.split(" ").filter(Boolean)
: [];
const [isContentVisible, setIsContentVisible] = useState(false); const [isContentVisible, setIsContentVisible] = useState(false);
const hasChildren = !!children; const hasChildren = !!children;
return ( return (
<div <DyadCard
className={`bg-(--background-lightest) dark:bg-gray-900 hover:bg-(--background-lighter) rounded-lg px-4 py-3 border my-2 border-border ${ accentColor="blue"
hasChildren ? "cursor-pointer" : "" isExpanded={isContentVisible}
}`}
onClick={ onClick={
hasChildren ? () => setIsContentVisible(!isContentVisible) : undefined hasChildren ? () => setIsContentVisible(!isContentVisible) : undefined
} }
> >
<div className="flex items-center justify-between mb-2"> <DyadCardHeader icon={<Package size={15} />} accentColor="blue">
<div className="flex items-center gap-2"> <DyadBadge color="blue">Add Packages</DyadBadge>
<Package size={18} className="text-gray-600 dark:text-gray-400" />
{packages.length > 0 && (
<div className="text-gray-800 dark:text-gray-200 font-semibold text-base">
<div className="font-normal">
Do you want to install these packages?
</div>{" "}
<div className="flex flex-wrap gap-2 mt-2">
{packages.map((p: string) => (
<span
className="cursor-pointer text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
key={p}
onClick={() => {
ipc.system.openExternalUrl(
`https://www.npmjs.com/package/${p}`,
);
}}
>
{p}
</span>
))}
</div>
</div>
)}
</div>
{hasChildren && ( {hasChildren && (
<div className="flex items-center"> <div className="ml-auto">
{isContentVisible ? ( <DyadExpandIcon isExpanded={isContentVisible} />
<ChevronsDownUp
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
) : (
<ChevronsUpDown
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)}
</div> </div>
)} )}
</div> </DyadCardHeader>
{packages.length > 0 && ( {packages.length > 0 && (
<div className="text-sm text-gray-600 dark:text-gray-400 mb-1"> <div className="px-3 pb-2">
Make sure these packages are what you want.{" "} <div className="text-sm text-foreground mb-1">
Do you want to install these packages?
</div>
<div className="flex flex-wrap gap-1.5 mt-1.5">
{packages.map((p: string) => (
<span
className="cursor-pointer text-sm px-2 py-0.5 rounded-md bg-blue-50 dark:bg-blue-950/40 text-blue-600 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900/60 ring-1 ring-inset ring-blue-200 dark:ring-blue-800 transition-colors"
key={p}
onClick={(e) => {
e.stopPropagation();
ipc.system.openExternalUrl(
`https://www.npmjs.com/package/${p}`,
);
}}
>
{p}
</span>
))}
</div>
<div className="text-xs text-muted-foreground mt-2">
Make sure these packages are what you want.
</div>
</div> </div>
)} )}
<DyadCardContent isExpanded={isContentVisible}>
{/* Show content if it's visible and has children */} {hasChildren && (
{isContentVisible && hasChildren && (
<div className="mt-2">
<div className="text-xs"> <div className="text-xs">
<CodeHighlight className="language-shell">{children}</CodeHighlight> <CodeHighlight className="language-shell">{children}</CodeHighlight>
</div> </div>
</div> )}
)} </DyadCardContent>
</div> </DyadCard>
); );
}; };
...@@ -7,6 +7,8 @@ import { useAtomValue } from "jotai"; ...@@ -7,6 +7,8 @@ import { useAtomValue } from "jotai";
import { showError } from "@/lib/toast"; import { showError } from "@/lib/toast";
import { useLoadApp } from "@/hooks/useLoadApp"; import { useLoadApp } from "@/hooks/useLoadApp";
import { useStreamChat } from "@/hooks/useStreamChat"; import { useStreamChat } from "@/hooks/useStreamChat";
import { CheckCircle2, Plug } from "lucide-react";
import { DyadCard, DyadCardHeader, DyadBadge } from "./DyadCardPrimitives";
interface DyadAddIntegrationProps { interface DyadAddIntegrationProps {
node: { node: {
...@@ -50,65 +52,47 @@ export const DyadAddIntegration: React.FC<DyadAddIntegrationProps> = ({ ...@@ -50,65 +52,47 @@ export const DyadAddIntegration: React.FC<DyadAddIntegrationProps> = ({
if (app?.supabaseProjectName) { if (app?.supabaseProjectName) {
return ( return (
<div className="flex flex-col my-2 p-3 border border-green-300 dark:border-green-800/50 rounded-lg bg-green-50 dark:bg-green-900/20 shadow-sm"> <DyadCard accentColor="green" state="finished">
<div className="flex items-center space-x-2"> <DyadCardHeader icon={<CheckCircle2 size={15} />} accentColor="green">
<svg <DyadBadge color="green">Integration Complete</DyadBadge>
className="w-5 h-5 text-green-600 dark:text-green-400" <span className="text-sm font-medium text-foreground">
fill="none"
stroke="currentColor"
strokeWidth={2}
viewBox="0 0 24 24"
>
<circle
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="2"
fill="currentColor"
className="opacity-20"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M9 12l2 2 4-4"
/>
</svg>
<span className="font-semibold text-green-800 dark:text-green-300">
Supabase integration complete Supabase integration complete
</span> </span>
</div> </DyadCardHeader>
<div className="text-sm text-green-900 dark:text-green-100"> <div className="px-3 pb-3">
<p> <p className="text-sm text-muted-foreground mb-2">
This app is connected to Supabase project:{" "} This app is connected to Supabase project:{" "}
<span className="font-mono font-medium bg-green-100 dark:bg-green-900/40 px-1 py-0.5 rounded"> <span className="font-mono font-medium px-1.5 py-0.5 rounded bg-green-100 dark:bg-green-900/40 text-green-800 dark:text-green-200">
{app.supabaseProjectName} {app.supabaseProjectName}
</span> </span>
</p> </p>
<Button
onClick={handleKeepGoingClick}
variant="default"
disabled={isStreaming}
size="sm"
>
Continue
</Button>
</div> </div>
<Button </DyadCard>
onClick={handleKeepGoingClick}
className="self-start mt-2"
variant="default"
disabled={isStreaming}
>
Continue
</Button>
</div>
); );
} }
return ( return (
<div className="flex flex-col gap-2 my-2 p-3 border rounded-md bg-secondary/10 dark:bg-secondary/20"> <DyadCard accentColor="blue">
<div className="text-sm"> <DyadCardHeader icon={<Plug size={15} />} accentColor="blue">
<div className="font-medium text-foreground"> <DyadBadge color="blue">Integration</DyadBadge>
<span className="text-sm font-medium text-foreground">
Integrate with {provider}? Integrate with {provider}?
</div> </span>
<div className="text-muted-foreground text-xs">{children}</div> </DyadCardHeader>
<div className="px-3 pb-3">
<div className="text-xs text-muted-foreground mb-3">{children}</div>
<Button onClick={handleSetupClick} className="w-full" size="sm">
Set up {provider}
</Button>
</div> </div>
<Button onClick={handleSetupClick} className="self-start w-full"> </DyadCard>
Set up {provider}
</Button>
</div>
); );
}; };
import React, { useEffect, useState } from "react";
import { ChevronRight, Loader2, CircleX, CheckCircle2 } from "lucide-react";
import { CustomTagState } from "./stateTypes";
/**
* Accent color configuration for DyadCard components.
* Maps to Tailwind color classes for border, background, and text.
*/
export type DyadAccentColor =
| "blue"
| "purple"
| "violet"
| "red"
| "amber"
| "green"
| "emerald"
| "teal"
| "sky"
| "indigo"
| "slate";
const ACCENT_BORDER: Record<DyadAccentColor, string> = {
blue: "border-l-blue-500",
purple: "border-l-purple-500",
violet: "border-l-violet-500",
red: "border-l-red-500",
amber: "border-l-amber-500",
green: "border-l-green-500",
emerald: "border-l-emerald-500",
teal: "border-l-teal-500",
sky: "border-l-sky-500",
indigo: "border-l-indigo-500",
slate: "border-l-slate-400",
};
const ACCENT_ICON_BG: Record<DyadAccentColor, string> = {
blue: "bg-blue-100 dark:bg-blue-950 text-blue-600 dark:text-blue-400",
purple:
"bg-purple-100 dark:bg-purple-950 text-purple-600 dark:text-purple-400",
violet:
"bg-violet-100 dark:bg-violet-950 text-violet-600 dark:text-violet-400",
red: "bg-red-100 dark:bg-red-950 text-red-600 dark:text-red-400",
amber: "bg-amber-100 dark:bg-amber-950 text-amber-600 dark:text-amber-400",
green: "bg-green-100 dark:bg-green-950 text-green-600 dark:text-green-400",
emerald:
"bg-emerald-100 dark:bg-emerald-950 text-emerald-600 dark:text-emerald-400",
teal: "bg-teal-100 dark:bg-teal-950 text-teal-600 dark:text-teal-400",
sky: "bg-sky-100 dark:bg-sky-950 text-sky-600 dark:text-sky-400",
indigo:
"bg-indigo-100 dark:bg-indigo-950 text-indigo-600 dark:text-indigo-400",
slate: "bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400",
};
const ACCENT_BADGE: Record<DyadAccentColor, string> = {
blue: "bg-blue-50 dark:bg-blue-950/60 text-blue-700 dark:text-blue-300 ring-blue-200 dark:ring-blue-800",
purple:
"bg-purple-50 dark:bg-purple-950/60 text-purple-700 dark:text-purple-300 ring-purple-200 dark:ring-purple-800",
violet:
"bg-violet-50 dark:bg-violet-950/60 text-violet-700 dark:text-violet-300 ring-violet-200 dark:ring-violet-800",
red: "bg-red-50 dark:bg-red-950/60 text-red-700 dark:text-red-300 ring-red-200 dark:ring-red-800",
amber:
"bg-amber-50 dark:bg-amber-950/60 text-amber-700 dark:text-amber-300 ring-amber-200 dark:ring-amber-800",
green:
"bg-green-50 dark:bg-green-950/60 text-green-700 dark:text-green-300 ring-green-200 dark:ring-green-800",
emerald:
"bg-emerald-50 dark:bg-emerald-950/60 text-emerald-700 dark:text-emerald-300 ring-emerald-200 dark:ring-emerald-800",
teal: "bg-teal-50 dark:bg-teal-950/60 text-teal-700 dark:text-teal-300 ring-teal-200 dark:ring-teal-800",
sky: "bg-sky-50 dark:bg-sky-950/60 text-sky-700 dark:text-sky-300 ring-sky-200 dark:ring-sky-800",
indigo:
"bg-indigo-50 dark:bg-indigo-950/60 text-indigo-700 dark:text-indigo-300 ring-indigo-200 dark:ring-indigo-800",
slate:
"bg-slate-50 dark:bg-slate-900/60 text-slate-700 dark:text-slate-300 ring-slate-200 dark:ring-slate-700",
};
// -- DyadCard --
interface DyadCardProps {
children: React.ReactNode;
state?: CustomTagState;
accentColor?: DyadAccentColor;
showAccent?: boolean;
variant?: "default" | "ghost";
onClick?: () => void;
isExpanded?: boolean;
className?: string;
"data-testid"?: string;
}
/**
* Premium container for all Dyad markdown action cards.
* Provides consistent borders, backgrounds, hover states, and a colored
* left-accent border when the action is pending or aborted (or when
* `showAccent` is explicitly set).
*
* When `onClick` is provided, the card behaves as an interactive button
* with keyboard support (Enter/Space) and appropriate ARIA attributes.
*/
export function DyadCard({
children,
state,
accentColor = "blue",
showAccent,
variant = "default",
onClick,
isExpanded,
className = "",
...props
}: DyadCardProps) {
const isPending = state === "pending";
const isAborted = state === "aborted";
const shouldShowAccent = showAccent ?? (isPending || isAborted);
const leftBorder = shouldShowAccent
? `border-l-[3px] ${isAborted ? "border-l-red-500" : ACCENT_BORDER[accentColor]}`
: "";
const variantClasses =
variant === "ghost"
? "hover:bg-(--background-lightest) rounded-lg"
: `bg-(--background-lightest) hover:bg-(--background-lighter) rounded-xl border border-border/60 ${leftBorder}`;
return (
<div
className={`
group/card
${variantClasses}
my-1.5 transition-colors duration-150
${onClick ? "cursor-pointer" : ""}
${className}
`}
onClick={onClick}
role={onClick ? "button" : undefined}
tabIndex={onClick ? 0 : undefined}
aria-expanded={
onClick && isExpanded !== undefined ? isExpanded : undefined
}
onKeyDown={
onClick
? (e) => {
if (
(e.key === "Enter" || e.key === " ") &&
e.target === e.currentTarget
) {
e.preventDefault();
onClick();
}
}
: undefined
}
{...props}
>
{children}
</div>
);
}
// -- DyadCardHeader --
interface DyadCardHeaderProps {
icon: React.ReactNode;
accentColor?: DyadAccentColor;
children?: React.ReactNode;
}
/**
* Header row for DyadCard. Contains a tinted icon circle and flexible content area.
*/
export function DyadCardHeader({
icon,
accentColor = "blue",
children,
}: DyadCardHeaderProps) {
return (
<div className="flex items-center gap-2.5 px-3 py-2">
<div
className={`flex items-center justify-center size-7 rounded-lg shrink-0 ${ACCENT_ICON_BG[accentColor]}`}
>
{icon}
</div>
<div className="flex-1 min-w-0 flex items-center gap-2">{children}</div>
</div>
);
}
// -- DyadBadge --
interface DyadBadgeProps {
children: React.ReactNode;
color?: DyadAccentColor;
}
/**
* Small pill badge for labeling card types (e.g. "GREP", "Turbo Edit", "SQL").
*/
export function DyadBadge({ children, color = "blue" }: DyadBadgeProps) {
return (
<span
className={`inline-flex items-center text-[11px] font-semibold px-1.5 py-0.5 rounded-md ring-1 ring-inset ${ACCENT_BADGE[color]}`}
>
{children}
</span>
);
}
// -- DyadExpandIcon --
interface DyadExpandIconProps {
isExpanded: boolean;
}
/**
* Animated chevron icon for expand/collapse. Rotates 90 degrees when expanded.
*/
export function DyadExpandIcon({ isExpanded }: DyadExpandIconProps) {
return (
<ChevronRight
size={16}
className={`shrink-0 text-muted-foreground transition-transform duration-200 ${
isExpanded ? "rotate-90" : ""
}`}
/>
);
}
// -- DyadStateIndicator --
interface DyadStateIndicatorProps {
state: CustomTagState;
pendingLabel?: string;
abortedLabel?: string;
finishedLabel?: string;
}
/**
* Renders a spinner (pending), X icon (aborted), or checkmark (finished).
* Includes an optional text label for each state.
*/
export function DyadStateIndicator({
state,
pendingLabel,
abortedLabel,
finishedLabel,
}: DyadStateIndicatorProps) {
if (state === "pending") {
return (
<span className="inline-flex items-center gap-1 text-amber-600 dark:text-amber-400 text-xs shrink-0">
<Loader2 size={14} className="animate-spin" />
{pendingLabel && <span>{pendingLabel}</span>}
</span>
);
}
if (state === "aborted") {
return (
<span className="inline-flex items-center gap-1 text-red-500 dark:text-red-400 text-xs shrink-0">
<CircleX size={14} />
{abortedLabel && <span>{abortedLabel}</span>}
</span>
);
}
if (state === "finished") {
return (
<span className="inline-flex items-center gap-1 text-green-600 dark:text-green-500 text-xs shrink-0">
<CheckCircle2 size={14} />
{finishedLabel && <span>{finishedLabel}</span>}
</span>
);
}
return null;
}
// -- DyadFinishedIcon --
/**
* Small green checkmark for completed state, useful for status-type cards.
*/
export function DyadFinishedIcon() {
return (
<CheckCircle2 className="size-4 text-green-600 dark:text-green-500 shrink-0" />
);
}
// -- DyadCardContent --
interface DyadCardContentProps {
isExpanded: boolean;
children: React.ReactNode;
className?: string;
}
/**
* Expandable content area using CSS grid for smooth height animation.
* Uses lazy mounting: children are only rendered after the first expansion,
* preventing heavy components from initializing when collapsed.
*/
export function DyadCardContent({
isExpanded,
children,
className = "",
}: DyadCardContentProps) {
const [hasExpanded, setHasExpanded] = useState(false);
useEffect(() => {
if (isExpanded && !hasExpanded) {
setHasExpanded(true);
}
}, [isExpanded]);
return (
<div
className={`grid transition-all duration-200 ease-in-out ${
isExpanded
? "grid-rows-[1fr] opacity-100"
: "grid-rows-[0fr] opacity-0 pointer-events-none"
} ${className}`}
>
<div className="overflow-hidden">
<div className="px-3 pb-3">{hasExpanded ? children : null}</div>
</div>
</div>
);
}
// -- DyadFilePath --
interface DyadFilePathProps {
path: string;
}
/**
* Styled file path display with monospace font and muted color.
*/
export function DyadFilePath({ path }: DyadFilePathProps) {
if (!path) return null;
return (
<div className="px-3 pb-1">
<span className="text-[11px] text-muted-foreground font-mono truncate block">
{path}
</span>
</div>
);
}
// -- DyadDescription --
interface DyadDescriptionProps {
children: React.ReactNode;
}
/**
* Description/summary text below the header.
*/
export function DyadDescription({ children }: DyadDescriptionProps) {
return (
<div className="px-3 pb-2 text-xs text-muted-foreground">{children}</div>
);
}
import type React from "react"; import type React from "react";
import { useState, type ReactNode } from "react"; import { useState, type ReactNode } from "react";
import { ChevronDown, ChevronUp, FileCode, Loader } from "lucide-react"; import { FileCode } from "lucide-react";
import { CustomTagState } from "./stateTypes"; import { CustomTagState } from "./stateTypes";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadExpandIcon,
DyadStateIndicator,
DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadCodeSearchProps { interface DyadCodeSearchProps {
children?: ReactNode; children?: ReactNode;
...@@ -19,78 +27,48 @@ export const DyadCodeSearch: React.FC<DyadCodeSearchProps> = ({ ...@@ -19,78 +27,48 @@ export const DyadCodeSearch: React.FC<DyadCodeSearchProps> = ({
const inProgress = state === "pending"; const inProgress = state === "pending";
return ( return (
<div <DyadCard
className={`bg-(--background-lightest) dark:bg-zinc-900 hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${ state={state}
inProgress ? "border-purple-500" : "border-border" accentColor="indigo"
}`}
onClick={() => setIsExpanded(!isExpanded)} onClick={() => setIsExpanded(!isExpanded)}
role="button" isExpanded={isExpanded}
aria-expanded={isExpanded}
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setIsExpanded(!isExpanded);
}
}}
> >
<div className="flex items-center justify-between"> <DyadCardHeader icon={<FileCode size={15} />} accentColor="indigo">
<div className="flex items-center gap-2"> <DyadBadge color="indigo">Code Search</DyadBadge>
<FileCode size={16} className="text-purple-600" /> {!isExpanded && query && (
<div className="text-xs text-purple-600 font-medium">Code Search</div> <span className="text-sm text-muted-foreground italic truncate">
{inProgress && ( {query}
<div className="flex items-center text-purple-600 text-xs"> </span>
<Loader size={14} className="mr-1 animate-spin" /> )}
<span>Searching...</span> {inProgress && (
</div> <DyadStateIndicator state="pending" pendingLabel="Searching..." />
)} )}
</div> <div className="ml-auto">
<div className="p-1 text-gray-500"> <DyadExpandIcon isExpanded={isExpanded} />
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
</div> </div>
</div> </DyadCardHeader>
<DyadCardContent isExpanded={isExpanded}>
{/* Collapsed preview - show query */} <div className="text-sm text-muted-foreground space-y-2">
<div
className="text-sm italic text-gray-600 dark:text-gray-300 mt-2 overflow-hidden transition-all duration-300 ease-in-out"
style={{
maxHeight: isExpanded ? "0px" : "3em",
opacity: isExpanded ? 0 : 1,
}}
>
{query}
</div>
{/* Expanded content */}
<div
className="overflow-hidden transition-all duration-300 ease-in-out"
style={{
maxHeight: isExpanded ? "none" : "0px",
opacity: isExpanded ? 1 : 0,
marginTop: isExpanded ? "0.5rem" : "0",
}}
>
<div className="text-sm text-gray-600 dark:text-gray-300 space-y-2">
{query && ( {query && (
<div> <div>
<span className="text-xs font-medium text-gray-500 dark:text-gray-400"> <span className="text-xs font-medium text-muted-foreground">
Query: Query:
</span> </span>
<div className="italic mt-0.5">{query}</div> <div className="italic mt-0.5 text-foreground">{query}</div>
</div> </div>
)} )}
{children && ( {children && (
<div> <div>
<span className="text-xs font-medium text-gray-500 dark:text-gray-400"> <span className="text-xs font-medium text-muted-foreground">
Results: Results:
</span> </span>
<div className="mt-0.5 whitespace-pre-wrap font-mono text-xs"> <div className="mt-0.5 whitespace-pre-wrap font-mono text-xs text-foreground">
{children} {children}
</div> </div>
</div> </div>
)} )}
</div> </div>
</div> </DyadCardContent>
</div> </DyadCard>
); );
}; };
import React, { useState, useMemo } from "react"; import React, { useState, useMemo } from "react";
import { ChevronDown, ChevronUp, FileCode, FileText } from "lucide-react"; import { FileCode, FileText } from "lucide-react";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadExpandIcon,
DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadCodeSearchResultProps { interface DyadCodeSearchResultProps {
node?: any; node?: any;
...@@ -11,7 +18,6 @@ export const DyadCodeSearchResult: React.FC<DyadCodeSearchResultProps> = ({ ...@@ -11,7 +18,6 @@ export const DyadCodeSearchResult: React.FC<DyadCodeSearchResultProps> = ({
}) => { }) => {
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
// Parse file paths from children content
const files = useMemo(() => { const files = useMemo(() => {
if (typeof children !== "string") { if (typeof children !== "string") {
return []; return [];
...@@ -22,7 +28,6 @@ export const DyadCodeSearchResult: React.FC<DyadCodeSearchResultProps> = ({ ...@@ -22,7 +28,6 @@ export const DyadCodeSearchResult: React.FC<DyadCodeSearchResultProps> = ({
for (const line of lines) { for (const line of lines) {
const trimmedLine = line.trim(); const trimmedLine = line.trim();
// Skip empty lines and lines that look like tags
if ( if (
trimmedLine && trimmedLine &&
!trimmedLine.startsWith("<") && !trimmedLine.startsWith("<") &&
...@@ -36,88 +41,53 @@ export const DyadCodeSearchResult: React.FC<DyadCodeSearchResultProps> = ({ ...@@ -36,88 +41,53 @@ export const DyadCodeSearchResult: React.FC<DyadCodeSearchResultProps> = ({
}, [children]); }, [children]);
return ( return (
<div <DyadCard
className="relative bg-(--background-lightest) dark:bg-zinc-900 hover:bg-(--background-lighter) rounded-lg px-4 py-2 border border-border my-2 cursor-pointer" accentColor="indigo"
isExpanded={isExpanded}
onClick={() => setIsExpanded(!isExpanded)} onClick={() => setIsExpanded(!isExpanded)}
role="button"
aria-expanded={isExpanded}
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setIsExpanded(!isExpanded);
}
}}
> >
{/* Top-left label badge */} <DyadCardHeader icon={<FileCode size={15} />} accentColor="indigo">
<div <DyadBadge color="indigo">Code Search Result</DyadBadge>
className="absolute top-2 left-2 flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold text-purple-600 bg-white dark:bg-zinc-900" {files.length > 0 && (
style={{ zIndex: 1 }} <span className="text-xs text-muted-foreground">
>
<FileCode size={16} className="text-purple-600" />
<span>Code Search Result</span>
</div>
{/* File count when collapsed */}
{files.length > 0 && (
<div className="absolute top-2 left-44 flex items-center">
<span className="px-1.5 py-0.5 bg-gray-100 dark:bg-zinc-800 text-xs rounded text-gray-600 dark:text-gray-300">
Found {files.length} file{files.length !== 1 ? "s" : ""} Found {files.length} file{files.length !== 1 ? "s" : ""}
</span> </span>
)}
<div className="ml-auto">
<DyadExpandIcon isExpanded={isExpanded} />
</div> </div>
)} </DyadCardHeader>
<DyadCardContent isExpanded={isExpanded}>
{/* Indicator icon */}
<div className="absolute top-2 right-2 p-1 text-gray-500">
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
</div>
{/* Main content with smooth transition */}
<div
className="pt-6 overflow-hidden transition-all duration-300 ease-in-out"
style={{
maxHeight: isExpanded ? "1000px" : "0px",
opacity: isExpanded ? 1 : 0,
marginBottom: isExpanded ? "0" : "-6px",
}}
>
{/* File list when expanded */}
{files.length > 0 && ( {files.length > 0 && (
<div className="mb-3"> <div className="flex flex-wrap gap-1.5">
<div className="flex flex-wrap gap-2 mt-2"> {files.map((file, index) => {
{files.map((file, index) => { const filePath = file.trim();
const filePath = file.trim(); const fileName = filePath.split("/").pop() || filePath;
const fileName = filePath.split("/").pop() || filePath; const pathPart =
const pathPart = filePath.substring(0, filePath.length - fileName.length) || "";
filePath.substring(0, filePath.length - fileName.length) ||
"";
return ( return (
<div <div key={index} className="px-2 py-1 bg-muted/40 rounded-lg">
key={index} <div className="flex items-center gap-1.5">
className="px-2 py-1 bg-gray-100 dark:bg-zinc-800 rounded-lg" <FileText
> size={13}
<div className="flex items-center gap-1.5"> className="text-muted-foreground shrink-0"
<FileText />
size={14} <span className="text-sm font-medium text-foreground">
className="text-gray-500 dark:text-gray-400 flex-shrink-0" {fileName}
/> </span>
<div className="text-sm font-medium text-gray-700 dark:text-gray-300">
{fileName}
</div>
</div>
{pathPart && (
<div className="text-xs text-gray-500 dark:text-gray-400 ml-5">
{pathPart}
</div>
)}
</div> </div>
); {pathPart && (
})} <div className="text-[11px] text-muted-foreground ml-5 font-mono">
</div> {pathPart}
</div>
)}
</div>
);
})}
</div> </div>
)} )}
</div> </DyadCardContent>
</div> </DyadCard>
); );
}; };
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { ChevronUp, ChevronDown, Code2, FileText } from "lucide-react"; import { Code2, FileText } from "lucide-react";
import { CustomTagState } from "./stateTypes"; import { CustomTagState } from "./stateTypes";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadExpandIcon,
DyadStateIndicator,
DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadCodebaseContextProps { interface DyadCodebaseContextProps {
children: React.ReactNode; children: React.ReactNode;
...@@ -20,7 +28,6 @@ export const DyadCodebaseContext: React.FC<DyadCodebaseContextProps> = ({ ...@@ -20,7 +28,6 @@ export const DyadCodebaseContext: React.FC<DyadCodebaseContextProps> = ({
const [isExpanded, setIsExpanded] = useState(inProgress); const [isExpanded, setIsExpanded] = useState(inProgress);
const files = node?.properties?.files?.split(",") || []; const files = node?.properties?.files?.split(",") || [];
// Collapse when transitioning from in-progress to not-in-progress
useEffect(() => { useEffect(() => {
if (!inProgress && isExpanded) { if (!inProgress && isExpanded) {
setIsExpanded(false); setIsExpanded(false);
...@@ -28,90 +35,55 @@ export const DyadCodebaseContext: React.FC<DyadCodebaseContextProps> = ({ ...@@ -28,90 +35,55 @@ export const DyadCodebaseContext: React.FC<DyadCodebaseContextProps> = ({
}, [inProgress]); }, [inProgress]);
return ( return (
<div <DyadCard
className={`relative bg-(--background-lightest) dark:bg-zinc-900 hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${ state={state}
inProgress ? "border-blue-500" : "border-border" accentColor="blue"
}`}
onClick={() => setIsExpanded(!isExpanded)} onClick={() => setIsExpanded(!isExpanded)}
role="button" isExpanded={isExpanded}
aria-expanded={isExpanded}
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setIsExpanded(!isExpanded);
}
}}
> >
{/* Top-left label badge */} <DyadCardHeader icon={<Code2 size={15} />} accentColor="blue">
<div <DyadBadge color="blue">Codebase Context</DyadBadge>
className="absolute top-2 left-2 flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold text-blue-500 bg-white dark:bg-zinc-900" {files.length > 0 && (
style={{ zIndex: 1 }} <span className="text-xs text-muted-foreground">
>
<Code2 size={16} className="text-blue-500" />
<span>Codebase Context</span>
</div>
{/* File count when collapsed */}
{files.length > 0 && (
<div className="absolute top-2 left-40 flex items-center">
<span className="px-1.5 py-0.5 bg-gray-100 dark:bg-zinc-800 text-xs rounded text-gray-600 dark:text-gray-300">
Using {files.length} file{files.length !== 1 ? "s" : ""} Using {files.length} file{files.length !== 1 ? "s" : ""}
</span> </span>
)}
{inProgress && <DyadStateIndicator state="pending" />}
<div className="ml-auto">
<DyadExpandIcon isExpanded={isExpanded} />
</div> </div>
)} </DyadCardHeader>
<DyadCardContent isExpanded={isExpanded}>
{/* Indicator icon */}
<div className="absolute top-2 right-2 p-1 text-gray-500">
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
</div>
{/* Main content with smooth transition */}
<div
className="pt-6 overflow-hidden transition-all duration-300 ease-in-out"
style={{
maxHeight: isExpanded ? "1000px" : "0px",
opacity: isExpanded ? 1 : 0,
marginBottom: isExpanded ? "0" : "-6px", // Compensate for padding
}}
>
{/* File list when expanded */}
{files.length > 0 && ( {files.length > 0 && (
<div className="mb-3"> <div className="flex flex-wrap gap-1.5">
<div className="flex flex-wrap gap-2 mt-2"> {files.map((file, index) => {
{files.map((file, index) => { const filePath = file.trim();
const filePath = file.trim(); const fileName = filePath.split("/").pop() || filePath;
const fileName = filePath.split("/").pop() || filePath; const pathPart =
const pathPart = filePath.substring(0, filePath.length - fileName.length) || "";
filePath.substring(0, filePath.length - fileName.length) ||
"";
return ( return (
<div <div key={index} className="px-2 py-1 bg-muted/40 rounded-lg">
key={index} <div className="flex items-center gap-1.5">
className="px-2 py-1 bg-gray-100 dark:bg-zinc-800 rounded-lg" <FileText
> size={13}
<div className="flex items-center gap-1.5"> className="text-muted-foreground shrink-0"
<FileText />
size={14} <span className="text-sm font-medium text-foreground">
className="text-gray-500 dark:text-gray-400 flex-shrink-0" {fileName}
/> </span>
<div className="text-sm font-medium text-gray-700 dark:text-gray-300">
{fileName}
</div>
</div>
{pathPart && (
<div className="text-xs text-gray-500 dark:text-gray-400 ml-5">
{pathPart}
</div>
)}
</div> </div>
); {pathPart && (
})} <div className="text-[11px] text-muted-foreground ml-5 font-mono">
</div> {pathPart}
</div>
)}
</div>
);
})}
</div> </div>
)} )}
</div> </DyadCardContent>
</div> </DyadCard>
); );
}; };
import React from "react"; import React from "react";
import { CustomTagState } from "./stateTypes"; import { CustomTagState } from "./stateTypes";
import { Database, Loader2 } from "lucide-react"; import { Database } from "lucide-react";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadStateIndicator,
} from "./DyadCardPrimitives";
interface DyadDatabaseSchemaProps { interface DyadDatabaseSchemaProps {
node: { node: {
...@@ -20,20 +26,18 @@ export function DyadDatabaseSchema({ ...@@ -20,20 +26,18 @@ export function DyadDatabaseSchema({
const content = typeof children === "string" ? children : ""; const content = typeof children === "string" ? children : "";
return ( return (
<div className="my-2 border rounded-md overflow-hidden"> <DyadCard state={state} accentColor="teal">
<div className="flex items-center gap-2 px-3 py-2 bg-muted/50 border-b"> <DyadCardHeader icon={<Database size={15} />} accentColor="teal">
{isLoading ? ( <DyadBadge color="teal">Database Schema</DyadBadge>
<Loader2 className="size-4 animate-spin text-muted-foreground" /> {isLoading && <DyadStateIndicator state="pending" />}
) : ( </DyadCardHeader>
<Database className="size-4 text-muted-foreground" />
)}
<span className="font-medium text-sm">Database Schema</span>
</div>
{content && ( {content && (
<div className="p-3 text-xs font-mono whitespace-pre-wrap max-h-60 overflow-y-auto bg-muted/20"> <div className="px-3 pb-3">
{content} <div className="p-3 text-xs font-mono whitespace-pre-wrap max-h-60 overflow-y-auto bg-muted/20 rounded-lg">
{content}
</div>
</div> </div>
)} )}
</div> </DyadCard>
); );
} }
import type React from "react"; import type React from "react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { Trash2 } from "lucide-react"; import { Trash2 } from "lucide-react";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadFilePath,
DyadDescription,
} from "./DyadCardPrimitives";
import { CustomTagState } from "./stateTypes";
interface DyadDeleteProps { interface DyadDeleteProps {
children?: ReactNode; children?: ReactNode;
...@@ -13,33 +21,22 @@ export const DyadDelete: React.FC<DyadDeleteProps> = ({ ...@@ -13,33 +21,22 @@ export const DyadDelete: React.FC<DyadDeleteProps> = ({
node, node,
path: pathProp, path: pathProp,
}) => { }) => {
// Use props directly if provided, otherwise extract from node
const path = pathProp || node?.properties?.path || ""; const path = pathProp || node?.properties?.path || "";
const state = node?.properties?.state as CustomTagState;
// Extract filename from path
const fileName = path ? path.split("/").pop() : ""; const fileName = path ? path.split("/").pop() : "";
return ( return (
<div className="bg-(--background-lightest) rounded-lg px-4 py-2 border border-red-500 my-2"> <DyadCard accentColor="red" state={state}>
<div className="flex items-center justify-between"> <DyadCardHeader icon={<Trash2 size={15} />} accentColor="red">
<div className="flex items-center gap-2"> {fileName && (
<Trash2 size={16} className="text-red-500" /> <span className="font-medium text-sm text-foreground truncate">
{fileName && ( {fileName}
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm"> </span>
{fileName} )}
</span> <DyadBadge color="red">Delete</DyadBadge>
)} </DyadCardHeader>
<div className="text-xs text-red-500 font-medium">Delete</div> <DyadFilePath path={path} />
</div> {children && <DyadDescription>{children}</DyadDescription>}
</div> </DyadCard>
{path && (
<div className="text-xs text-gray-500 dark:text-gray-400 font-medium mb-1">
{path}
</div>
)}
<div className="text-sm text-gray-600 dark:text-gray-300 mt-2">
{children}
</div>
</div>
); );
}; };
import type React from "react"; import type React from "react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { useState } from "react"; import { useState } from "react";
import { import { Zap } from "lucide-react";
ChevronsDownUp,
ChevronsUpDown,
Loader,
CircleX,
Rabbit,
} from "lucide-react";
import { CodeHighlight } from "./CodeHighlight"; import { CodeHighlight } from "./CodeHighlight";
import { CustomTagState } from "./stateTypes"; import { CustomTagState } from "./stateTypes";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadExpandIcon,
DyadStateIndicator,
DyadDescription,
DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadEditProps { interface DyadEditProps {
children?: ReactNode; children?: ReactNode;
...@@ -26,79 +29,54 @@ export const DyadEdit: React.FC<DyadEditProps> = ({ ...@@ -26,79 +29,54 @@ export const DyadEdit: React.FC<DyadEditProps> = ({
}) => { }) => {
const [isContentVisible, setIsContentVisible] = useState(false); const [isContentVisible, setIsContentVisible] = useState(false);
// Use props directly if provided, otherwise extract from node
const path = pathProp || node?.properties?.path || ""; const path = pathProp || node?.properties?.path || "";
const description = descriptionProp || node?.properties?.description || ""; const description = descriptionProp || node?.properties?.description || "";
const state = node?.properties?.state as CustomTagState; const state = node?.properties?.state as CustomTagState;
const inProgress = state === "pending"; const inProgress = state === "pending";
const aborted = state === "aborted"; const aborted = state === "aborted";
// Extract filename from path
const fileName = path ? path.split("/").pop() : ""; const fileName = path ? path.split("/").pop() : "";
return ( return (
<div <DyadCard
className={`bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${ state={state}
inProgress accentColor="sky"
? "border-amber-500"
: aborted
? "border-red-500"
: "border-border"
}`}
onClick={() => setIsContentVisible(!isContentVisible)} onClick={() => setIsContentVisible(!isContentVisible)}
isExpanded={isContentVisible}
> >
<div className="flex items-center justify-between"> <DyadCardHeader icon={<Zap size={15} />} accentColor="sky">
<div className="flex items-center gap-2"> <div className="min-w-0 truncate">
<div className="flex items-center">
<Rabbit size={16} />
<span className="bg-blue-500 text-white text-xs px-1.5 py-0.5 rounded ml-1 font-medium">
Turbo Edit
</span>
</div>
{fileName && ( {fileName && (
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm"> <span className="font-medium text-sm text-foreground truncate block">
{fileName} {fileName}
</span> </span>
)} )}
{inProgress && ( {path && (
<div className="flex items-center text-amber-600 text-xs"> <span className="text-[11px] text-muted-foreground truncate block">
<Loader size={14} className="mr-1 animate-spin" /> {path}
<span>Editing...</span> </span>
</div>
)}
{aborted && (
<div className="flex items-center text-red-600 text-xs">
<CircleX size={14} className="mr-1" />
<span>Did not finish</span>
</div>
)}
</div>
<div className="flex items-center">
{isContentVisible ? (
<ChevronsDownUp
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
) : (
<ChevronsUpDown
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)} )}
</div> </div>
</div> {inProgress && (
{path && ( <DyadStateIndicator state="pending" pendingLabel="Editing..." />
<div className="text-xs text-gray-500 dark:text-gray-400 font-medium mb-1"> )}
{path} {aborted && (
<DyadStateIndicator state="aborted" abortedLabel="Did not finish" />
)}
<div className="ml-auto flex items-center gap-1">
<DyadBadge color="sky">Turbo Edit</DyadBadge>
<DyadExpandIcon isExpanded={isContentVisible} />
</div> </div>
)} </DyadCardHeader>
{description && ( {description && (
<div className="text-sm text-gray-600 dark:text-gray-300"> <DyadDescription>
<span className="font-medium">Summary: </span> <span className={!isContentVisible ? "line-clamp-2" : undefined}>
{description} <span className="font-medium">Summary: </span>
</div> {description}
</span>
</DyadDescription>
)} )}
{isContentVisible && ( <DyadCardContent isExpanded={isContentVisible}>
<div <div
className="text-xs cursor-text" className="text-xs cursor-text"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
...@@ -107,7 +85,7 @@ export const DyadEdit: React.FC<DyadEditProps> = ({ ...@@ -107,7 +85,7 @@ export const DyadEdit: React.FC<DyadEditProps> = ({
{children} {children}
</CodeHighlight> </CodeHighlight>
</div> </div>
)} </DyadCardContent>
</div> </DyadCard>
); );
}; };
import type React from "react"; import type React from "react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { useState } from "react"; import { useState } from "react";
import { import { Database } from "lucide-react";
ChevronsDownUp,
ChevronsUpDown,
Database,
Loader,
CircleX,
} from "lucide-react";
import { CodeHighlight } from "./CodeHighlight"; import { CodeHighlight } from "./CodeHighlight";
import { CustomTagState } from "./stateTypes"; import { CustomTagState } from "./stateTypes";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadExpandIcon,
DyadStateIndicator,
DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadExecuteSqlProps { interface DyadExecuteSqlProps {
children?: ReactNode; children?: ReactNode;
...@@ -29,57 +31,34 @@ export const DyadExecuteSql: React.FC<DyadExecuteSqlProps> = ({ ...@@ -29,57 +31,34 @@ export const DyadExecuteSql: React.FC<DyadExecuteSqlProps> = ({
const queryDescription = description || node?.properties?.description; const queryDescription = description || node?.properties?.description;
return ( return (
<div <DyadCard
className={`bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${ state={state}
inProgress accentColor="teal"
? "border-amber-500" isExpanded={isContentVisible}
: aborted
? "border-red-500"
: "border-border"
}`}
onClick={() => setIsContentVisible(!isContentVisible)} onClick={() => setIsContentVisible(!isContentVisible)}
> >
<div className="flex items-center justify-between"> <DyadCardHeader icon={<Database size={15} />} accentColor="teal">
<div className="flex items-center gap-2"> <DyadBadge color="teal">SQL</DyadBadge>
<Database size={16} /> {queryDescription && (
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm"> <span className="font-medium text-sm text-foreground truncate">
<span className="font-bold mr-2 outline-2 outline-gray-200 dark:outline-gray-700 bg-gray-100 dark:bg-gray-800 rounded-md px-1">
SQL
</span>
{queryDescription} {queryDescription}
</span> </span>
{inProgress && ( )}
<div className="flex items-center text-amber-600 text-xs"> {inProgress && (
<Loader size={14} className="mr-1 animate-spin" /> <DyadStateIndicator state="pending" pendingLabel="Executing..." />
<span>Executing...</span> )}
</div> {aborted && (
)} <DyadStateIndicator state="aborted" abortedLabel="Did not finish" />
{aborted && ( )}
<div className="flex items-center text-red-600 text-xs"> <div className="ml-auto">
<CircleX size={14} className="mr-1" /> <DyadExpandIcon isExpanded={isContentVisible} />
<span>Did not finish</span>
</div>
)}
</div>
<div className="flex items-center">
{isContentVisible ? (
<ChevronsDownUp
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
) : (
<ChevronsUpDown
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)}
</div> </div>
</div> </DyadCardHeader>
{isContentVisible && ( <DyadCardContent isExpanded={isContentVisible}>
<div className="text-xs"> <div className="text-xs">
<CodeHighlight className="language-sql">{children}</CodeHighlight> <CodeHighlight className="language-sql">{children}</CodeHighlight>
</div> </div>
)} </DyadCardContent>
</div> </DyadCard>
); );
}; };
import type React from "react"; import type React from "react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { useState } from "react"; import { useState } from "react";
import { import { Search } from "lucide-react";
ChevronsDownUp,
ChevronsUpDown,
Search,
Loader,
CircleX,
} from "lucide-react";
import { CodeHighlight } from "./CodeHighlight"; import { CodeHighlight } from "./CodeHighlight";
import { CustomTagState } from "./stateTypes"; import { CustomTagState } from "./stateTypes";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadExpandIcon,
DyadStateIndicator,
DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadGrepProps { interface DyadGrepProps {
children?: ReactNode; children?: ReactNode;
...@@ -30,12 +32,10 @@ interface DyadGrepProps { ...@@ -30,12 +32,10 @@ interface DyadGrepProps {
export const DyadGrep: React.FC<DyadGrepProps> = ({ children, node }) => { export const DyadGrep: React.FC<DyadGrepProps> = ({ children, node }) => {
const [isContentVisible, setIsContentVisible] = useState(false); const [isContentVisible, setIsContentVisible] = useState(false);
// State handling
const state = node?.properties?.state as CustomTagState; const state = node?.properties?.state as CustomTagState;
const inProgress = state === "pending"; const inProgress = state === "pending";
const aborted = state === "aborted"; const aborted = state === "aborted";
// Get properties from node
const query = node?.properties?.query || ""; const query = node?.properties?.query || "";
const includePattern = node?.properties?.include || ""; const includePattern = node?.properties?.include || "";
const excludePattern = node?.properties?.exclude || ""; const excludePattern = node?.properties?.exclude || "";
...@@ -43,9 +43,7 @@ export const DyadGrep: React.FC<DyadGrepProps> = ({ children, node }) => { ...@@ -43,9 +43,7 @@ export const DyadGrep: React.FC<DyadGrepProps> = ({ children, node }) => {
const count = node?.properties?.count || ""; const count = node?.properties?.count || "";
const total = node?.properties?.total || ""; const total = node?.properties?.total || "";
const truncated = node?.properties?.truncated === "true"; const truncated = node?.properties?.truncated === "true";
const hasResults = count !== "" && count !== "0";
// Build description
let description = `"${query}"`; let description = `"${query}"`;
if (includePattern) { if (includePattern) {
description += ` in ${includePattern}`; description += ` in ${includePattern}`;
...@@ -57,70 +55,45 @@ export const DyadGrep: React.FC<DyadGrepProps> = ({ children, node }) => { ...@@ -57,70 +55,45 @@ export const DyadGrep: React.FC<DyadGrepProps> = ({ children, node }) => {
description += " (case-sensitive)"; description += " (case-sensitive)";
} }
// Build result summary
const resultSummary = count const resultSummary = count
? truncated && total ? truncated && total
? `${count} of ${total} matches` ? `${count} of ${total} matches`
: `${count} match${count === "1" ? "" : "es"}` : `${count} match${count === "1" ? "" : "es"}`
: ""; : "";
// Dynamic border styling
const borderClass = inProgress
? "border-(--primary)"
: aborted
? "border-red-500"
: "border-(--primary)/30";
return ( return (
<div <DyadCard
data-testid="dyad-grep" state={state}
className={`bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${borderClass}`} accentColor="violet"
onClick={() => setIsContentVisible(!isContentVisible)} onClick={() => setIsContentVisible(!isContentVisible)}
isExpanded={isContentVisible}
data-testid="dyad-grep"
> >
<div className="flex items-center justify-between"> <DyadCardHeader icon={<Search size={15} />} accentColor="violet">
<div className="flex items-center gap-2"> <DyadBadge color="violet">GREP</DyadBadge>
<Search size={16} className="text-(--primary)" /> <span className="font-medium text-sm text-foreground truncate">
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm"> {description}
<span className="font-bold mr-2 outline-2 outline-(--primary)/20 bg-(--primary)/10 text-(--primary) rounded-md px-1"> </span>
GREP {resultSummary && (
</span> <span className="text-xs text-muted-foreground shrink-0">
{description} ({resultSummary})
{resultSummary && (
<span className="ml-2 text-gray-500">({resultSummary})</span>
)}
</span> </span>
{inProgress && ( )}
<div className="flex items-center text-(--primary) text-xs"> {inProgress && (
<Loader size={14} className="mr-1 animate-spin" /> <DyadStateIndicator state="pending" pendingLabel="Searching..." />
<span>Searching...</span> )}
</div> {aborted && (
)} <DyadStateIndicator state="aborted" abortedLabel="Did not finish" />
{aborted && ( )}
<div className="flex items-center text-red-600 text-xs"> <div className="ml-auto">
<CircleX size={14} className="mr-1" /> <DyadExpandIcon isExpanded={isContentVisible} />
<span>Did not finish</span>
</div>
)}
</div>
<div className="flex items-center">
{isContentVisible ? (
<ChevronsDownUp
size={20}
className="text-(--primary)/70 hover:text-(--primary)"
/>
) : (
<ChevronsUpDown
size={20}
className="text-(--primary)/70 hover:text-(--primary)"
/>
)}
</div> </div>
</div> </DyadCardHeader>
{isContentVisible && ( <DyadCardContent isExpanded={isContentVisible}>
<div className={`text-xs${hasResults ? " mt-2" : ""}`}> <div className="text-xs" onClick={(e) => e.stopPropagation()}>
<CodeHighlight className="language-log">{children}</CodeHighlight> <CodeHighlight className="language-log">{children}</CodeHighlight>
</div> </div>
)} </DyadCardContent>
</div> </DyadCard>
); );
}; };
import React, { useState } from "react"; import React, { useState } from "react";
import { CustomTagState } from "./stateTypes"; import { CustomTagState } from "./stateTypes";
import { ChevronRight, FolderOpen, Loader2 } from "lucide-react"; import { FolderOpen } from "lucide-react";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadExpandIcon,
DyadStateIndicator,
DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadListFilesProps { interface DyadListFilesProps {
node: { node: {
...@@ -22,45 +30,36 @@ export function DyadListFiles({ node, children }: DyadListFilesProps) { ...@@ -22,45 +30,36 @@ export function DyadListFiles({ node, children }: DyadListFilesProps) {
const content = typeof children === "string" ? children : ""; const content = typeof children === "string" ? children : "";
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
const getTitle = () => { const title = directory ? directory : "List Files";
const parts: string[] = ["List Files"];
if (directory) {
parts[0] = `List Files: ${directory}`;
}
if (isRecursive) {
parts.push("(recursive)");
}
if (isIncludeHidden) {
parts.push("(include hidden)");
}
return parts.join(" ");
};
return ( return (
<div <DyadCard
state={state}
accentColor="slate"
isExpanded={isExpanded}
onClick={() => setIsExpanded(!isExpanded)}
data-testid="dyad-list-files" data-testid="dyad-list-files"
className="my-2 border rounded-md overflow-hidden"
> >
<button <DyadCardHeader icon={<FolderOpen size={15} />} accentColor="slate">
type="button" <span className="font-medium text-sm text-foreground truncate">
onClick={() => setIsExpanded(!isExpanded)} {title}
className="flex items-center gap-2 px-3 py-2 bg-muted/50 w-full text-left hover:bg-muted/70 transition-colors" </span>
> {isRecursive && <DyadBadge color="slate">recursive</DyadBadge>}
<ChevronRight {isIncludeHidden && <DyadBadge color="slate">include hidden</DyadBadge>}
className={`size-4 text-muted-foreground transition-transform ${isExpanded ? "rotate-90" : ""}`} {isLoading && (
/> <DyadStateIndicator state="pending" pendingLabel="Listing..." />
{isLoading ? (
<Loader2 className="size-4 animate-spin text-muted-foreground" />
) : (
<FolderOpen className="size-4 text-muted-foreground" />
)} )}
<span className="font-medium text-sm">{getTitle()}</span> <div className="ml-auto">
</button> <DyadExpandIcon isExpanded={isExpanded} />
{isExpanded && content && (
<div className="p-3 text-xs font-mono whitespace-pre-wrap max-h-60 overflow-y-auto bg-muted/20 border-t">
{content}
</div> </div>
)} </DyadCardHeader>
</div> <DyadCardContent isExpanded={isExpanded}>
{content && (
<div className="p-3 text-xs font-mono whitespace-pre-wrap max-h-60 overflow-y-auto bg-muted/20 rounded-lg">
{content}
</div>
)}
</DyadCardContent>
</DyadCard>
); );
} }
import type React from "react"; import type React from "react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { useState } from "react"; import { useState } from "react";
import { import { FileText } from "lucide-react";
ChevronsDownUp,
ChevronsUpDown,
FileText,
Loader,
CircleX,
} from "lucide-react";
import { CodeHighlight } from "./CodeHighlight"; import { CodeHighlight } from "./CodeHighlight";
import { CustomTagState } from "./stateTypes"; import { CustomTagState } from "./stateTypes";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadExpandIcon,
DyadStateIndicator,
DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadLogsProps { interface DyadLogsProps {
children?: ReactNode; children?: ReactNode;
...@@ -19,16 +21,13 @@ interface DyadLogsProps { ...@@ -19,16 +21,13 @@ interface DyadLogsProps {
export const DyadLogs: React.FC<DyadLogsProps> = ({ children, node }) => { export const DyadLogs: React.FC<DyadLogsProps> = ({ children, node }) => {
const [isContentVisible, setIsContentVisible] = useState(false); const [isContentVisible, setIsContentVisible] = useState(false);
// State handling
const state = node?.properties?.state as CustomTagState; const state = node?.properties?.state as CustomTagState;
const inProgress = state === "pending"; const inProgress = state === "pending";
const aborted = state === "aborted"; const aborted = state === "aborted";
// Get count from node properties
const logCount = node?.properties?.count || ""; const logCount = node?.properties?.count || "";
const hasResults = !!logCount; const hasResults = !!logCount;
// Build description based on filters
const logType = node?.properties?.type || "all"; const logType = node?.properties?.type || "all";
const logLevel = node?.properties?.level || "all"; const logLevel = node?.properties?.level || "all";
const filters: string[] = []; const filters: string[] = [];
...@@ -36,62 +35,35 @@ export const DyadLogs: React.FC<DyadLogsProps> = ({ children, node }) => { ...@@ -36,62 +35,35 @@ export const DyadLogs: React.FC<DyadLogsProps> = ({ children, node }) => {
if (logLevel !== "all") filters.push(`level: ${logLevel}`); if (logLevel !== "all") filters.push(`level: ${logLevel}`);
const filterDesc = filters.length > 0 ? ` (${filters.join(", ")})` : ""; const filterDesc = filters.length > 0 ? ` (${filters.join(", ")})` : "";
// Build display text
const displayText = `Reading ${hasResults ? `${logCount} ` : ""}logs${filterDesc}`; const displayText = `Reading ${hasResults ? `${logCount} ` : ""}logs${filterDesc}`;
// Dynamic border styling
const borderClass = inProgress
? "border-(--primary)"
: aborted
? "border-red-500"
: "border-(--primary)/30";
return ( return (
<div <DyadCard
className={`bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${borderClass}`} state={state}
accentColor="slate"
isExpanded={isContentVisible}
onClick={() => setIsContentVisible(!isContentVisible)} onClick={() => setIsContentVisible(!isContentVisible)}
> >
<div className="flex items-center justify-between"> <DyadCardHeader icon={<FileText size={15} />} accentColor="slate">
<div className="flex items-center gap-2"> <DyadBadge color="slate">LOGS</DyadBadge>
<FileText size={16} className="text-(--primary)" /> <span className="font-medium text-sm text-foreground truncate">
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm"> {displayText}
<span className="font-bold mr-2 outline-2 outline-(--primary)/20 bg-(--primary)/10 text-(--primary) rounded-md px-1"> </span>
LOGS {inProgress && (
</span> <DyadStateIndicator state="pending" pendingLabel="Reading..." />
{displayText} )}
</span> {aborted && (
{inProgress && ( <DyadStateIndicator state="aborted" abortedLabel="Did not finish" />
<div className="flex items-center text-(--primary) text-xs"> )}
<Loader size={14} className="mr-1 animate-spin" /> <div className="ml-auto">
<span>Reading...</span> <DyadExpandIcon isExpanded={isContentVisible} />
</div>
)}
{aborted && (
<div className="flex items-center text-red-600 text-xs">
<CircleX size={14} className="mr-1" />
<span>Did not finish</span>
</div>
)}
</div>
<div className="flex items-center">
{isContentVisible ? (
<ChevronsDownUp
size={20}
className="text-(--primary)/70 hover:text-(--primary)"
/>
) : (
<ChevronsUpDown
size={20}
className="text-(--primary)/70 hover:text-(--primary)"
/>
)}
</div> </div>
</div> </DyadCardHeader>
{isContentVisible && ( <DyadCardContent isExpanded={isContentVisible}>
<div className={`text-xs${hasResults ? " mt-2" : ""}`}> <div className="text-xs">
<CodeHighlight className="language-log">{children}</CodeHighlight> <CodeHighlight className="language-log">{children}</CodeHighlight>
</div> </div>
)} </DyadCardContent>
</div> </DyadCard>
); );
}; };
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import { Wrench, ChevronsUpDown, ChevronsDownUp } from "lucide-react"; import { Wrench } from "lucide-react";
import { CodeHighlight } from "./CodeHighlight"; import { CodeHighlight } from "./CodeHighlight";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadExpandIcon,
DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadMcpToolCallProps { interface DyadMcpToolCallProps {
node?: any; node?: any;
...@@ -29,45 +36,30 @@ export const DyadMcpToolCall: React.FC<DyadMcpToolCallProps> = ({ ...@@ -29,45 +36,30 @@ export const DyadMcpToolCall: React.FC<DyadMcpToolCallProps> = ({
}, [expanded, raw]); }, [expanded, raw]);
return ( return (
<div <DyadCard
className="relative bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer" accentColor="blue"
isExpanded={expanded}
onClick={() => setExpanded((v) => !v)} onClick={() => setExpanded((v) => !v)}
> >
{/* Top-left label badge */} <DyadCardHeader icon={<Wrench size={15} />} accentColor="blue">
<div <DyadBadge color="blue">Tool Call</DyadBadge>
className="absolute top-3 left-2 flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold text-blue-600 bg-white dark:bg-zinc-900" {serverName && (
style={{ zIndex: 1 }} <span className="text-xs px-2 py-0.5 rounded-full bg-blue-50 dark:bg-blue-950/40 text-blue-700 dark:text-blue-300 ring-1 ring-inset ring-blue-200 dark:ring-blue-800">
>
<Wrench size={16} className="text-blue-600" />
<span>Tool Call</span>
</div>
{/* Right chevron */}
<div className="absolute top-2 right-2 p-1 text-gray-500">
{expanded ? <ChevronsDownUp size={18} /> : <ChevronsUpDown size={18} />}
</div>
{/* Header content */}
<div className="flex items-start gap-2 pl-24 pr-8 py-1">
{serverName ? (
<span className="text-xs px-2 py-0.5 rounded-full bg-blue-50 dark:bg-zinc-800 text-blue-700 dark:text-blue-300 border border-blue-200 dark:border-zinc-700">
{serverName} {serverName}
</span> </span>
) : null} )}
{toolName ? ( {toolName && (
<span className="text-xs px-2 py-0.5 rounded-full bg-gray-100 dark:bg-zinc-800 text-gray-700 dark:text-gray-200 border border-border"> <span className="text-xs px-2 py-0.5 rounded-full bg-muted/50 text-muted-foreground ring-1 ring-inset ring-border">
{toolName} {toolName}
</span> </span>
) : null} )}
{/* Intentionally no preview or content when collapsed */} <div className="ml-auto">
</div> <DyadExpandIcon isExpanded={expanded} />
{/* JSON content */}
{expanded ? (
<div className="mt-2 pr-4 pb-2">
<CodeHighlight className="language-json">{prettyJson}</CodeHighlight>
</div> </div>
) : null} </DyadCardHeader>
</div> <DyadCardContent isExpanded={expanded}>
<CodeHighlight className="language-json">{prettyJson}</CodeHighlight>
</DyadCardContent>
</DyadCard>
); );
}; };
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import { CheckCircle, ChevronsUpDown, ChevronsDownUp } from "lucide-react"; import { CheckCircle } from "lucide-react";
import { CodeHighlight } from "./CodeHighlight"; import { CodeHighlight } from "./CodeHighlight";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadExpandIcon,
DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadMcpToolResultProps { interface DyadMcpToolResultProps {
node?: any; node?: any;
...@@ -29,45 +36,30 @@ export const DyadMcpToolResult: React.FC<DyadMcpToolResultProps> = ({ ...@@ -29,45 +36,30 @@ export const DyadMcpToolResult: React.FC<DyadMcpToolResultProps> = ({
}, [expanded, raw]); }, [expanded, raw]);
return ( return (
<div <DyadCard
className="relative bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer" accentColor="emerald"
isExpanded={expanded}
onClick={() => setExpanded((v) => !v)} onClick={() => setExpanded((v) => !v)}
> >
{/* Top-left label badge */} <DyadCardHeader icon={<CheckCircle size={15} />} accentColor="emerald">
<div <DyadBadge color="emerald">Tool Result</DyadBadge>
className="absolute top-3 left-2 flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold text-emerald-600 bg-white dark:bg-zinc-900" {serverName && (
style={{ zIndex: 1 }} <span className="text-xs px-2 py-0.5 rounded-full bg-emerald-50 dark:bg-emerald-950/40 text-emerald-700 dark:text-emerald-300 ring-1 ring-inset ring-emerald-200 dark:ring-emerald-800">
>
<CheckCircle size={16} className="text-emerald-600" />
<span>Tool Result</span>
</div>
{/* Right chevron */}
<div className="absolute top-2 right-2 p-1 text-gray-500">
{expanded ? <ChevronsDownUp size={18} /> : <ChevronsUpDown size={18} />}
</div>
{/* Header content */}
<div className="flex items-start gap-2 pl-24 pr-8 py-1">
{serverName ? (
<span className="text-xs px-2 py-0.5 rounded-full bg-emerald-50 dark:bg-zinc-800 text-emerald-700 dark:text-emerald-300 border border-emerald-200 dark:border-zinc-700">
{serverName} {serverName}
</span> </span>
) : null} )}
{toolName ? ( {toolName && (
<span className="text-xs px-2 py-0.5 rounded-full bg-gray-100 dark:bg-zinc-800 text-gray-700 dark:text-gray-200 border border-border"> <span className="text-xs px-2 py-0.5 rounded-full bg-muted/50 text-muted-foreground ring-1 ring-inset ring-border">
{toolName} {toolName}
</span> </span>
) : null} )}
{/* Intentionally no preview or content when collapsed */} <div className="ml-auto">
</div> <DyadExpandIcon isExpanded={expanded} />
{/* JSON content */}
{expanded ? (
<div className="mt-2 pr-4 pb-2">
<CodeHighlight className="language-json">{prettyJson}</CodeHighlight>
</div> </div>
) : null} </DyadCardHeader>
</div> <DyadCardContent isExpanded={expanded}>
<CodeHighlight className="language-json">{prettyJson}</CodeHighlight>
</DyadCardContent>
</DyadCard>
); );
}; };
import React, { useState } from "react"; import React, { useState } from "react";
import { import { AlertTriangle, XCircle, Sparkles } from "lucide-react";
ChevronsDownUp,
ChevronsUpDown,
AlertTriangle,
XCircle,
Sparkles,
} from "lucide-react";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { selectedChatIdAtom } from "@/atoms/chatAtoms"; import { selectedChatIdAtom } from "@/atoms/chatAtoms";
import { useStreamChat } from "@/hooks/useStreamChat"; import { useStreamChat } from "@/hooks/useStreamChat";
import { CopyErrorMessage } from "@/components/CopyErrorMessage"; import { CopyErrorMessage } from "@/components/CopyErrorMessage";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadExpandIcon,
DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadOutputProps { interface DyadOutputProps {
type: "error" | "warning"; type: "error" | "warning";
message?: string; message?: string;
...@@ -27,13 +29,8 @@ export const DyadOutput: React.FC<DyadOutputProps> = ({ ...@@ -27,13 +29,8 @@ export const DyadOutput: React.FC<DyadOutputProps> = ({
// If the type is not warning, it is an error (in case LLM gives a weird "type") // If the type is not warning, it is an error (in case LLM gives a weird "type")
const isError = type !== "warning"; const isError = type !== "warning";
const borderColor = isError ? "border-red-500" : "border-amber-500"; const accentColor = isError ? "red" : "amber";
const iconColor = isError ? "text-red-500" : "text-amber-500"; const icon = isError ? <XCircle size={15} /> : <AlertTriangle size={15} />;
const icon = isError ? (
<XCircle size={16} className={iconColor} />
) : (
<AlertTriangle size={16} className={iconColor} />
);
const label = isError ? "Error" : "Warning"; const label = isError ? "Error" : "Warning";
const handleAIFix = (e: React.MouseEvent) => { const handleAIFix = (e: React.MouseEvent) => {
...@@ -47,66 +44,47 @@ export const DyadOutput: React.FC<DyadOutputProps> = ({ ...@@ -47,66 +44,47 @@ export const DyadOutput: React.FC<DyadOutputProps> = ({
}; };
return ( return (
<div <DyadCard
className={`relative bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer min-h-18 ${borderColor}`} showAccent
accentColor={accentColor}
onClick={() => setIsContentVisible(!isContentVisible)} onClick={() => setIsContentVisible(!isContentVisible)}
isExpanded={isContentVisible}
> >
{/* Top-left label badge */} <DyadCardHeader icon={icon} accentColor={accentColor}>
<div <DyadBadge color={accentColor}>{label}</DyadBadge>
className={`absolute top-2 left-2 flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold ${iconColor} bg-white dark:bg-gray-900`} {message && (
style={{ zIndex: 1 }} <span className="text-sm text-foreground truncate">
> {message.slice(0, isContentVisible ? undefined : 100) +
{icon} (!isContentVisible && message.length > 100 ? "..." : "")}
<span>{label}</span> </span>
</div> )}
<div className="ml-auto">
{/* Main content, padded to avoid label */} <DyadExpandIcon isExpanded={isContentVisible} />
<div className="flex items-center justify-between pl-24 pr-6">
<div className="flex items-center gap-2">
{message && (
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm">
{message.slice(0, isContentVisible ? undefined : 100) +
(!isContentVisible ? "..." : "")}
</span>
)}
</div>
<div className="flex items-center">
{isContentVisible ? (
<ChevronsDownUp
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
) : (
<ChevronsUpDown
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)}
</div> </div>
</div> </DyadCardHeader>
{/* Content area */} {/* Content area */}
{isContentVisible && children && ( <DyadCardContent isExpanded={isContentVisible}>
<div className="mt-4 pl-20 text-sm text-gray-800 dark:text-gray-200"> {children && (
{children} <div className="text-sm text-muted-foreground mb-3">{children}</div>
</div> )}
)} </DyadCardContent>
{/* Action buttons at the bottom - always visible for errors */} {/* Action buttons at the bottom - always visible for errors */}
{isError && message && ( {isError && message && (
<div className="mt-3 px-6 flex justify-end gap-2"> <div className="px-3 pb-2 flex justify-end gap-2">
<CopyErrorMessage <CopyErrorMessage
errorMessage={children ? `${message}\n${children}` : message} errorMessage={children ? `${message}\n${children}` : message}
/> />
<button <button
onClick={handleAIFix} onClick={handleAIFix}
className="cursor-pointer flex items-center justify-center bg-red-600 hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-800 text-white rounded text-xs px-2 py-1 h-6" className="cursor-pointer flex items-center justify-center bg-red-600 hover:bg-red-700 dark:bg-red-700 dark:hover:bg-red-800 text-white rounded-md text-xs px-2.5 py-1 h-6 transition-colors"
> >
<Sparkles size={14} className="mr-1" /> <Sparkles size={13} className="mr-1" />
<span>Fix with AI</span> <span>Fix with AI</span>
</button> </button>
</div> </div>
)} )}
</div> </DyadCard>
); );
}; };
import React, { useState } from "react"; import React, { useState } from "react";
import { import { AlertTriangle, FileText } from "lucide-react";
ChevronsDownUp,
ChevronsUpDown,
AlertTriangle,
FileText,
} from "lucide-react";
import type { Problem } from "@/ipc/types"; import type { Problem } from "@/ipc/types";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadExpandIcon,
DyadCardContent,
} from "./DyadCardPrimitives";
type ProblemWithoutSnippet = Omit<Problem, "snippet">; type ProblemWithoutSnippet = Omit<Problem, "snippet">;
...@@ -21,27 +23,26 @@ interface ProblemItemProps { ...@@ -21,27 +23,26 @@ interface ProblemItemProps {
const ProblemItem: React.FC<ProblemItemProps> = ({ problem, index }) => { const ProblemItem: React.FC<ProblemItemProps> = ({ problem, index }) => {
return ( return (
<div className="flex items-start gap-3 py-2 px-3 border-b border-gray-200 dark:border-gray-700 last:border-b-0"> <div className="flex items-start gap-3 py-2 px-3 border-b border-border/40 last:border-b-0">
<div className="flex-shrink-0 w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-800 flex items-center justify-center mt-0.5"> <div className="flex-shrink-0 size-6 rounded-full bg-muted/60 flex items-center justify-center mt-0.5">
<span className="text-xs font-medium text-gray-600 dark:text-gray-400"> <span className="text-[11px] font-semibold text-muted-foreground">
{index + 1} {index + 1}
</span> </span>
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-1.5">
<FileText size={14} className="text-gray-500 flex-shrink-0" /> <FileText size={13} className="text-muted-foreground shrink-0" />
<span className="text-sm font-medium text-gray-900 dark:text-gray-100 truncate"> <span className="text-sm font-medium text-foreground truncate">
{problem.file} {problem.file}
</span> </span>
<span className="text-[11px] text-muted-foreground font-mono">
<span className="text-xs text-gray-500 dark:text-gray-400">
{problem.line}:{problem.column} {problem.line}:{problem.column}
</span> </span>
<span className="text-xs bg-gray-100 dark:bg-gray-800 px-2 py-0.5 rounded text-gray-600 dark:text-gray-300"> <span className="text-[11px] bg-muted/50 px-1.5 py-0.5 rounded text-muted-foreground font-mono">
TS{problem.code} TS{problem.code}
</span> </span>
</div> </div>
<p className="text-sm text-gray-700 dark:text-gray-300 leading-relaxed"> <p className="text-sm text-muted-foreground leading-relaxed">
{problem.message} {problem.message}
</p> </p>
</div> </div>
...@@ -59,7 +60,6 @@ export const DyadProblemSummary: React.FC<DyadProblemSummaryProps> = ({ ...@@ -59,7 +60,6 @@ export const DyadProblemSummary: React.FC<DyadProblemSummaryProps> = ({
const problems: ProblemWithoutSnippet[] = React.useMemo(() => { const problems: ProblemWithoutSnippet[] = React.useMemo(() => {
if (!children || typeof children !== "string") return []; if (!children || typeof children !== "string") return [];
// Parse structured format with <problem> tags
const problemTagRegex = const problemTagRegex =
/<problem\s+file="([^"]+)"\s+line="(\d+)"\s+column="(\d+)"\s+code="(\d+)">([^<]+)<\/problem>/g; /<problem\s+file="([^"]+)"\s+line="(\d+)"\s+column="(\d+)"\s+code="(\d+)">([^<]+)<\/problem>/g;
const problems: ProblemWithoutSnippet[] = []; const problems: ProblemWithoutSnippet[] = [];
...@@ -95,43 +95,26 @@ export const DyadProblemSummary: React.FC<DyadProblemSummaryProps> = ({ ...@@ -95,43 +95,26 @@ export const DyadProblemSummary: React.FC<DyadProblemSummaryProps> = ({
summary || `${totalProblems} problems found (TypeScript errors)`; summary || `${totalProblems} problems found (TypeScript errors)`;
return ( return (
<div <DyadCard
className="bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border border-border my-2 cursor-pointer" accentColor="amber"
isExpanded={isContentVisible}
onClick={() => setIsContentVisible(!isContentVisible)} onClick={() => setIsContentVisible(!isContentVisible)}
data-testid="problem-summary" data-testid="problem-summary"
> >
<div className="flex items-center justify-between"> <DyadCardHeader icon={<AlertTriangle size={15} />} accentColor="amber">
<div className="flex items-center gap-2"> <DyadBadge color="amber">Auto-fix</DyadBadge>
<AlertTriangle <span className="font-medium text-sm text-foreground truncate">
size={16} {displaySummary}
className="text-amber-600 dark:text-amber-500" </span>
/> <div className="ml-auto">
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm"> <DyadExpandIcon isExpanded={isContentVisible} />
<span className="font-bold mr-2 outline-2 outline-amber-200 dark:outline-amber-700 bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-200 rounded-md px-1">
Auto-fix
</span>
{displaySummary}
</span>
</div>
<div className="flex items-center">
{isContentVisible ? (
<ChevronsDownUp
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
) : (
<ChevronsUpDown
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)}
</div> </div>
</div> </DyadCardHeader>
{/* Content area - show individual problems */} {/* Content area - show individual problems */}
{isContentVisible && totalProblems > 0 && ( <DyadCardContent isExpanded={isContentVisible}>
<div className="mt-4"> {totalProblems > 0 ? (
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden"> <div className="bg-muted/20 rounded-lg border border-border/40 overflow-hidden">
{problems.map((problem, index) => ( {problems.map((problem, index) => (
<ProblemItem <ProblemItem
key={`${problem.file}-${problem.line}-${problem.column}-${index}`} key={`${problem.file}-${problem.line}-${problem.column}-${index}`}
...@@ -140,17 +123,14 @@ export const DyadProblemSummary: React.FC<DyadProblemSummaryProps> = ({ ...@@ -140,17 +123,14 @@ export const DyadProblemSummary: React.FC<DyadProblemSummaryProps> = ({
/> />
))} ))}
</div> </div>
</div> ) : (
)} children && (
<pre className="whitespace-pre-wrap font-mono text-xs bg-muted/20 p-3 rounded-lg text-muted-foreground">
{/* Fallback content area for raw children */} {children}
{isContentVisible && totalProblems === 0 && children && ( </pre>
<div className="mt-4 text-sm text-gray-800 dark:text-gray-200"> )
<pre className="whitespace-pre-wrap font-mono text-xs bg-gray-100 dark:bg-gray-800 p-3 rounded"> )}
{children} </DyadCardContent>
</pre> </DyadCard>
</div>
)}
</div>
); );
}; };
...@@ -15,29 +15,26 @@ export const DyadRead: React.FC<DyadReadProps> = ({ ...@@ -15,29 +15,26 @@ export const DyadRead: React.FC<DyadReadProps> = ({
}) => { }) => {
const path = pathProp || node?.properties?.path || ""; const path = pathProp || node?.properties?.path || "";
const fileName = path ? path.split("/").pop() : ""; const fileName = path ? path.split("/").pop() : "";
const dirPath = path
? path.slice(0, path.length - (fileName?.length || 0))
: "";
return ( return (
<div className="bg-(--background-lightest) rounded-lg px-4 py-2 border border-border my-2"> <div className="my-1">
<div className="flex items-center justify-between"> <div className="flex items-center gap-1 py-1">
<div className="flex items-center gap-2"> <FileText size={14} className="shrink-0 text-muted-foreground/50" />
<FileText size={16} className="text-gray-600" /> <span className="text-[13px] font-medium text-foreground/70">Read</span>
{fileName && ( {path && (
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm"> <span className="text-[13px] truncate min-w-0" title={path}>
{fileName} {dirPath && (
</span> <span className="text-muted-foreground/85">{dirPath}</span>
)} )}
<div className="text-xs text-gray-600 font-medium">Read</div> <span className="font-medium text-foreground/70">{fileName}</span>
</div> </span>
)}
</div> </div>
{path && (
<div className="text-xs text-gray-500 dark:text-gray-400 font-medium mb-1">
{path}
</div>
)}
{children && ( {children && (
<div className="text-sm text-gray-600 dark:text-gray-300 mt-2"> <div className="text-xs text-muted-foreground ml-5">{children}</div>
{children}
</div>
)} )}
</div> </div>
); );
......
import type React from "react"; import type React from "react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { FileEdit } from "lucide-react"; import { FileEdit } from "lucide-react";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadFilePath,
DyadDescription,
} from "./DyadCardPrimitives";
import { CustomTagState } from "./stateTypes";
interface DyadRenameProps { interface DyadRenameProps {
children?: ReactNode; children?: ReactNode;
...@@ -15,47 +23,31 @@ export const DyadRename: React.FC<DyadRenameProps> = ({ ...@@ -15,47 +23,31 @@ export const DyadRename: React.FC<DyadRenameProps> = ({
from: fromProp, from: fromProp,
to: toProp, to: toProp,
}) => { }) => {
// Use props directly if provided, otherwise extract from node
const from = fromProp || node?.properties?.from || ""; const from = fromProp || node?.properties?.from || "";
const to = toProp || node?.properties?.to || ""; const to = toProp || node?.properties?.to || "";
const state = node?.properties?.state as CustomTagState;
// Extract filenames from paths
const fromFileName = from ? from.split("/").pop() : ""; const fromFileName = from ? from.split("/").pop() : "";
const toFileName = to ? to.split("/").pop() : ""; const toFileName = to ? to.split("/").pop() : "";
const displayTitle =
fromFileName && toFileName
? `${fromFileName}${toFileName}`
: fromFileName || toFileName || "";
return ( return (
<div className="bg-(--background-lightest) rounded-lg px-4 py-2 border border-amber-500 my-2"> <DyadCard accentColor="amber" state={state}>
<div className="flex items-center justify-between"> <DyadCardHeader icon={<FileEdit size={15} />} accentColor="amber">
<div className="flex items-center gap-2"> {displayTitle && (
<FileEdit size={16} className="text-amber-500" /> <span className="font-medium text-sm text-foreground truncate">
{(fromFileName || toFileName) && ( {displayTitle}
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm"> </span>
{fromFileName && toFileName )}
? `${fromFileName} → ${toFileName}` <DyadBadge color="amber">Rename</DyadBadge>
: fromFileName || toFileName} </DyadCardHeader>
</span> {from && <DyadFilePath path={`From: ${from}`} />}
)} {to && <DyadFilePath path={`To: ${to}`} />}
<div className="text-xs text-amber-500 font-medium">Rename</div> {children && <DyadDescription>{children}</DyadDescription>}
</div> </DyadCard>
</div>
{(from || to) && (
<div className="flex flex-col text-xs text-gray-500 dark:text-gray-400 font-medium mb-1">
{from && (
<div>
<span className="text-gray-500 dark:text-gray-400">From:</span>{" "}
{from}
</div>
)}
{to && (
<div>
<span className="text-gray-500 dark:text-gray-400">To:</span> {to}
</div>
)}
</div>
)}
<div className="text-sm text-gray-600 dark:text-gray-300 mt-2">
{children}
</div>
</div>
); );
}; };
import type React from "react"; import type React from "react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { import { Search, ArrowLeftRight } from "lucide-react";
ChevronsDownUp,
ChevronsUpDown,
Loader,
CircleX,
Search,
ArrowLeftRight,
} from "lucide-react";
import { CodeHighlight } from "./CodeHighlight"; import { CodeHighlight } from "./CodeHighlight";
import { CustomTagState } from "./stateTypes"; import { CustomTagState } from "./stateTypes";
import { parseSearchReplaceBlocks } from "@/pro/shared/search_replace_parser"; import { parseSearchReplaceBlocks } from "@/pro/shared/search_replace_parser";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadExpandIcon,
DyadStateIndicator,
DyadFilePath,
DyadDescription,
DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadSearchReplaceProps { interface DyadSearchReplaceProps {
children?: ReactNode; children?: ReactNode;
...@@ -42,69 +45,41 @@ export const DyadSearchReplace: React.FC<DyadSearchReplaceProps> = ({ ...@@ -42,69 +45,41 @@ export const DyadSearchReplace: React.FC<DyadSearchReplaceProps> = ({
const fileName = path ? path.split("/").pop() : ""; const fileName = path ? path.split("/").pop() : "";
return ( return (
<div <DyadCard
data-testid="dyad-search-replace" state={state}
className={`bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${ accentColor="violet"
inProgress isExpanded={isContentVisible}
? "border-amber-500"
: aborted
? "border-red-500"
: "border-border"
}`}
onClick={() => setIsContentVisible(!isContentVisible)} onClick={() => setIsContentVisible(!isContentVisible)}
data-testid="dyad-search-replace"
> >
<div className="flex items-center justify-between"> <DyadCardHeader icon={<Search size={15} />} accentColor="violet">
<div className="flex items-center gap-2"> <DyadBadge color="violet">Search & Replace</DyadBadge>
<div className="flex items-center"> {fileName && (
<Search size={16} /> <span className="font-medium text-sm text-foreground truncate">
<span className="bg-purple-600 text-white text-xs px-1.5 py-0.5 rounded ml-1 font-medium"> {fileName}
Search & Replace </span>
</span> )}
</div> {inProgress && (
{fileName && ( <DyadStateIndicator
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm"> state="pending"
{fileName} pendingLabel="Applying changes..."
</span> />
)} )}
{inProgress && ( {aborted && (
<div className="flex items-center text-amber-600 text-xs"> <DyadStateIndicator state="aborted" abortedLabel="Did not finish" />
<Loader size={14} className="mr-1 animate-spin" /> )}
<span>Applying changes...</span> <div className="ml-auto">
</div> <DyadExpandIcon isExpanded={isContentVisible} />
)}
{aborted && (
<div className="flex items-center text-red-600 text-xs">
<CircleX size={14} className="mr-1" />
<span>Did not finish</span>
</div>
)}
</div> </div>
<div className="flex items-center"> </DyadCardHeader>
{isContentVisible ? ( <DyadFilePath path={path} />
<ChevronsDownUp
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
) : (
<ChevronsUpDown
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)}
</div>
</div>
{path && (
<div className="text-xs text-gray-500 dark:text-gray-400 font-medium mb-1">
{path}
</div>
)}
{description && ( {description && (
<div className="text-sm text-gray-600 dark:text-gray-300"> <DyadDescription>
<span className="font-medium">Summary: </span> <span className="font-medium">Summary: </span>
{description} {description}
</div> </DyadDescription>
)} )}
{isContentVisible && ( <DyadCardContent isExpanded={isContentVisible}>
<div <div
className="text-xs cursor-text" className="text-xs cursor-text"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
...@@ -114,17 +89,23 @@ export const DyadSearchReplace: React.FC<DyadSearchReplaceProps> = ({ ...@@ -114,17 +89,23 @@ export const DyadSearchReplace: React.FC<DyadSearchReplaceProps> = ({
{children} {children}
</CodeHighlight> </CodeHighlight>
) : ( ) : (
<div className="space-y-3"> <div className="space-y-2">
{blocks.map((b, i) => ( {blocks.map((b, i) => (
<div key={i} className="border rounded-lg"> <div
<div className="flex items-center justify-between px-3 py-2 bg-(--background-lighter) rounded-t-lg text-[11px]"> key={i}
<div className="flex items-center gap-2"> className="border border-border/60 rounded-lg overflow-hidden"
<ArrowLeftRight size={14} /> >
<span className="font-medium">Change {i + 1}</span> <div className="flex items-center gap-2 px-3 py-1.5 bg-muted/30 text-[11px]">
</div> <ArrowLeftRight
size={13}
className="text-muted-foreground"
/>
<span className="font-medium text-muted-foreground">
Change {i + 1}
</span>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-0"> <div className="grid grid-cols-1 md:grid-cols-2 gap-0">
<div className="p-3 border-t md:border-r"> <div className="p-3 border-t border-border/40 md:border-r">
<div className="text-[11px] mb-1 text-muted-foreground font-medium"> <div className="text-[11px] mb-1 text-muted-foreground font-medium">
Search Search
</div> </div>
...@@ -132,7 +113,7 @@ export const DyadSearchReplace: React.FC<DyadSearchReplaceProps> = ({ ...@@ -132,7 +113,7 @@ export const DyadSearchReplace: React.FC<DyadSearchReplaceProps> = ({
{b.searchContent} {b.searchContent}
</CodeHighlight> </CodeHighlight>
</div> </div>
<div className="p-3 border-t"> <div className="p-3 border-t border-border/40">
<div className="text-[11px] mb-1 text-muted-foreground font-medium"> <div className="text-[11px] mb-1 text-muted-foreground font-medium">
Replace Replace
</div> </div>
...@@ -146,7 +127,7 @@ export const DyadSearchReplace: React.FC<DyadSearchReplaceProps> = ({ ...@@ -146,7 +127,7 @@ export const DyadSearchReplace: React.FC<DyadSearchReplaceProps> = ({
</div> </div>
)} )}
</div> </div>
)} </DyadCardContent>
</div> </DyadCard>
); );
}; };
import React, { useState } from "react"; import React, { useState } from "react";
import { CustomTagState } from "./stateTypes"; import { CustomTagState } from "./stateTypes";
import { import {
Loader2, DyadCard,
CheckCircle2, DyadCardHeader,
XCircle, DyadExpandIcon,
ChevronsDownUp, DyadFinishedIcon,
ChevronsUpDown, DyadCardContent,
} from "lucide-react"; } from "./DyadCardPrimitives";
import { CircleX, Loader2 } from "lucide-react";
interface DyadStatusProps { interface DyadStatusProps {
node: { node: {
...@@ -22,61 +23,55 @@ export function DyadStatus({ node, children }: DyadStatusProps) { ...@@ -22,61 +23,55 @@ export function DyadStatus({ node, children }: DyadStatusProps) {
const { title = "Processing...", state } = node.properties; const { title = "Processing...", state } = node.properties;
const isInProgress = state === "pending"; const isInProgress = state === "pending";
const isAborted = state === "aborted"; const isAborted = state === "aborted";
const isFinished = state === "finished";
const content = typeof children === "string" ? children : ""; const content = typeof children === "string" ? children : "";
const [isContentVisible, setIsContentVisible] = useState(false); const [isContentVisible, setIsContentVisible] = useState(false);
// Pick accent color based on state
const accentColor = isAborted ? "red" : isInProgress ? "amber" : "green";
// Pick the left icon based on state
const icon = isInProgress ? (
<Loader2 size={15} className="animate-spin" />
) : isAborted ? (
<CircleX size={15} />
) : (
<DyadFinishedIcon />
);
return ( return (
<div <DyadCard
className={`bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${ state={state}
isInProgress accentColor={accentColor}
? "border-amber-500" isExpanded={isContentVisible}
: isAborted
? "border-red-500"
: "border-border"
}`}
onClick={() => setIsContentVisible(!isContentVisible)} onClick={() => setIsContentVisible(!isContentVisible)}
> >
<div className="flex items-center justify-between"> <DyadCardHeader icon={icon} accentColor={accentColor}>
<div className="flex items-center gap-2"> <span
{isInProgress ? ( className={`font-medium text-sm ${
<Loader2 className="size-4 animate-spin text-amber-600" /> isInProgress
) : isAborted ? ( ? "bg-gradient-to-r from-foreground via-muted-foreground to-foreground bg-[length:200%_100%] animate-[shimmer_2s_ease-in-out_infinite] bg-clip-text text-transparent"
<XCircle className="size-4 text-red-600" /> : isFinished
) : ( ? "text-foreground"
<CheckCircle2 className="size-4 text-green-600 dark:text-green-500" /> : "text-muted-foreground"
)} }`}
<span
className={`font-medium text-sm ${
isInProgress
? "bg-gradient-to-r from-foreground via-muted-foreground to-foreground bg-[length:200%_100%] animate-[shimmer_2s_ease-in-out_infinite] bg-clip-text text-transparent"
: "text-gray-700 dark:text-gray-300"
}`}
>
{title}
</span>
</div>
<div className="flex items-center">
{isContentVisible ? (
<ChevronsDownUp
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
) : (
<ChevronsUpDown
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)}
</div>
</div>
{isContentVisible && content && (
<div
className="mt-2 p-3 text-xs font-mono whitespace-pre-wrap max-h-60 overflow-y-auto bg-muted/20 rounded cursor-text"
onClick={(e) => e.stopPropagation()}
> >
{content} {title}
</span>
<div className="ml-auto">
<DyadExpandIcon isExpanded={isContentVisible} />
</div> </div>
)} </DyadCardHeader>
</div> <DyadCardContent isExpanded={isContentVisible}>
{content && (
<div
className="p-3 text-xs font-mono whitespace-pre-wrap max-h-60 overflow-y-auto bg-muted/20 rounded-lg cursor-text"
onClick={(e) => e.stopPropagation()}
>
{content}
</div>
)}
</DyadCardContent>
</DyadCard>
); );
} }
import React, { useState } from "react"; import React, { useState } from "react";
import { CustomTagState } from "./stateTypes"; import { CustomTagState } from "./stateTypes";
import { Database } from "lucide-react";
import { import {
Database, DyadCard,
Loader2, DyadCardHeader,
CircleX, DyadBadge,
ChevronsDownUp, DyadExpandIcon,
ChevronsUpDown, DyadStateIndicator,
} from "lucide-react"; DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadSupabaseProjectInfoProps { interface DyadSupabaseProjectInfoProps {
node: { node: {
...@@ -28,54 +30,31 @@ export function DyadSupabaseProjectInfo({ ...@@ -28,54 +30,31 @@ export function DyadSupabaseProjectInfo({
const content = typeof children === "string" ? children : ""; const content = typeof children === "string" ? children : "";
return ( return (
<div <DyadCard
className={`bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${ state={state}
isLoading accentColor="teal"
? "border-amber-500" isExpanded={isContentVisible}
: isAborted
? "border-red-500"
: "border-border"
}`}
onClick={() => setIsContentVisible(!isContentVisible)} onClick={() => setIsContentVisible(!isContentVisible)}
> >
<div className="flex items-center justify-between"> <DyadCardHeader icon={<Database size={15} />} accentColor="teal">
<div className="flex items-center gap-2"> <DyadBadge color="teal">Supabase Project Info</DyadBadge>
{isLoading ? ( {isLoading && (
<Loader2 className="size-4 animate-spin text-amber-600" /> <DyadStateIndicator state="pending" pendingLabel="Fetching..." />
) : isAborted ? ( )}
<CircleX className="size-4 text-red-500" /> {isAborted && (
) : ( <DyadStateIndicator state="aborted" abortedLabel="Did not finish" />
<Database className="size-4 text-muted-foreground" /> )}
)} <div className="ml-auto">
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm"> <DyadExpandIcon isExpanded={isContentVisible} />
Supabase Project Info
</span>
{isLoading && (
<span className="text-xs text-amber-600">Fetching...</span>
)}
{isAborted && (
<span className="text-xs text-red-500">Did not finish</span>
)}
</div> </div>
<div className="flex items-center"> </DyadCardHeader>
{isContentVisible ? ( <DyadCardContent isExpanded={isContentVisible}>
<ChevronsDownUp {content && (
size={20} <div className="p-3 text-xs font-mono whitespace-pre-wrap max-h-80 overflow-y-auto bg-muted/20 rounded-lg">
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" {content}
/> </div>
) : ( )}
<ChevronsUpDown </DyadCardContent>
size={20} </DyadCard>
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)}
</div>
</div>
{isContentVisible && content && (
<div className="mt-2 p-3 text-xs font-mono whitespace-pre-wrap max-h-80 overflow-y-auto bg-muted/30 rounded-md">
{content}
</div>
)}
</div>
); );
} }
import React, { useState } from "react"; import React, { useState } from "react";
import { CustomTagState } from "./stateTypes"; import { CustomTagState } from "./stateTypes";
import { Table2 } from "lucide-react";
import { import {
Table2, DyadCard,
Loader2, DyadCardHeader,
CircleX, DyadBadge,
ChevronsDownUp, DyadExpandIcon,
ChevronsUpDown, DyadStateIndicator,
} from "lucide-react"; DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadSupabaseTableSchemaProps { interface DyadSupabaseTableSchemaProps {
node: { node: {
...@@ -29,54 +31,38 @@ export function DyadSupabaseTableSchema({ ...@@ -29,54 +31,38 @@ export function DyadSupabaseTableSchema({
const content = typeof children === "string" ? children : ""; const content = typeof children === "string" ? children : "";
return ( return (
<div <DyadCard
className={`bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${ state={state}
isLoading accentColor="teal"
? "border-amber-500"
: isAborted
? "border-red-500"
: "border-border"
}`}
onClick={() => setIsContentVisible(!isContentVisible)} onClick={() => setIsContentVisible(!isContentVisible)}
isExpanded={isContentVisible}
> >
<div className="flex items-center justify-between"> <DyadCardHeader icon={<Table2 size={15} />} accentColor="teal">
<div className="flex items-center gap-2"> <DyadBadge color="teal">
{isLoading ? ( {table ? "Table Schema" : "Supabase Table Schema"}
<Loader2 className="size-4 animate-spin text-amber-600" /> </DyadBadge>
) : isAborted ? ( {table && (
<CircleX className="size-4 text-red-500" /> <span className="font-medium text-sm text-foreground truncate">
) : ( {table}
<Table2 className="size-4 text-muted-foreground" />
)}
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm">
{table ? `Table Schema: ${table}` : "Supabase Table Schema"}
</span> </span>
{isLoading && ( )}
<span className="text-xs text-amber-600">Fetching...</span> {isLoading && (
)} <DyadStateIndicator state="pending" pendingLabel="Fetching..." />
{isAborted && ( )}
<span className="text-xs text-red-500">Did not finish</span> {isAborted && (
)} <DyadStateIndicator state="aborted" abortedLabel="Did not finish" />
)}
<div className="ml-auto">
<DyadExpandIcon isExpanded={isContentVisible} />
</div> </div>
<div className="flex items-center"> </DyadCardHeader>
{isContentVisible ? ( <DyadCardContent isExpanded={isContentVisible}>
<ChevronsDownUp {content && (
size={20} <div className="p-3 text-xs font-mono whitespace-pre-wrap max-h-80 overflow-y-auto bg-muted/20 rounded-lg">
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" {content}
/> </div>
) : ( )}
<ChevronsUpDown </DyadCardContent>
size={20} </DyadCard>
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)}
</div>
</div>
{isContentVisible && content && (
<div className="mt-2 p-3 text-xs font-mono whitespace-pre-wrap max-h-80 overflow-y-auto bg-muted/30 rounded-md">
{content}
</div>
)}
</div>
); );
} }
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Brain, ChevronDown, ChevronUp, Loader } from "lucide-react"; import { ChevronRight } from "lucide-react";
import { VanillaMarkdownParser } from "./DyadMarkdownParser"; import { VanillaMarkdownParser } from "./DyadMarkdownParser";
import { CustomTagState } from "./stateTypes"; import { CustomTagState } from "./stateTypes";
import { DyadTokenSavings } from "./DyadTokenSavings"; import { DyadTokenSavings } from "./DyadTokenSavings";
...@@ -13,6 +13,13 @@ export const DyadThink: React.FC<DyadThinkProps> = ({ children, node }) => { ...@@ -13,6 +13,13 @@ export const DyadThink: React.FC<DyadThinkProps> = ({ children, node }) => {
const state = node?.properties?.state as CustomTagState; const state = node?.properties?.state as CustomTagState;
const inProgress = state === "pending"; const inProgress = state === "pending";
const [isExpanded, setIsExpanded] = useState(inProgress); const [isExpanded, setIsExpanded] = useState(inProgress);
const [hasExpanded, setHasExpanded] = useState(false);
useEffect(() => {
if (isExpanded && !hasExpanded) {
setHasExpanded(true);
}
}, [isExpanded]);
// Check if content matches token savings format // Check if content matches token savings format
const tokenSavingsMatch = const tokenSavingsMatch =
...@@ -41,54 +48,69 @@ export const DyadThink: React.FC<DyadThinkProps> = ({ children, node }) => { ...@@ -41,54 +48,69 @@ export const DyadThink: React.FC<DyadThinkProps> = ({ children, node }) => {
); );
} }
// Extract the first line for preview when collapsed
const firstLine =
typeof children === "string"
? (children
.split("\n")
.find((line) => line.trim() !== "")
?.trim()
.replace(/^\*{1,2}/, "")
.replace(/\*{1,2}$/, "") ?? "")
: "";
return ( return (
<div <div className="my-1">
className={`relative bg-(--background-lightest) dark:bg-zinc-900 hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${ {/* Toggle header */}
inProgress ? "border-purple-500" : "border-border" <button
}`} type="button"
onClick={() => setIsExpanded(!isExpanded)} onClick={() => setIsExpanded(!isExpanded)}
role="button" className="flex items-center gap-1 py-1 group cursor-pointer"
aria-expanded={isExpanded}
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setIsExpanded(!isExpanded);
}
}}
>
{/* Top-left label badge */}
<div
className="absolute top-2 left-2 flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold text-purple-500 bg-white dark:bg-zinc-900"
style={{ zIndex: 1 }}
> >
<Brain size={16} className="text-purple-500" /> <ChevronRight
<span>Thinking</span> size={14}
{inProgress && ( className={`shrink-0 text-muted-foreground/50 transition-transform duration-200 ${
<Loader size={14} className="ml-1 text-purple-500 animate-spin" /> isExpanded ? "rotate-90" : ""
}`}
/>
<span className="text-[13px] font-medium text-foreground/70 group-hover:text-foreground transition-colors">
{inProgress ? "Thinking…" : "Thought"}
</span>
{!isExpanded && firstLine && (
<span className="ml-0.5 truncate text-[13px] text-muted-foreground/85 max-w-md">
{firstLine}
</span>
)} )}
</div> </button>
{/* Indicator icon */}
<div className="absolute top-2 right-2 p-1 text-gray-500">
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
</div>
{/* Main content with smooth transition */} {/* Expandable content */}
<div <div
className="pt-6 overflow-hidden transition-all duration-300 ease-in-out" className={`grid transition-all duration-200 ease-in-out ${
style={{ isExpanded
maxHeight: isExpanded ? "none" : "0px", ? "grid-rows-[1fr] opacity-100"
opacity: isExpanded ? 1 : 0, : "grid-rows-[0fr] opacity-0"
marginBottom: isExpanded ? "0" : "-6px", // Compensate for padding }`}
}}
> >
<div className="px-0 text-sm text-gray-600 dark:text-gray-300"> <div className="overflow-hidden">
{typeof children === "string" ? ( <div className="flex ml-[7px]">
<VanillaMarkdownParser content={children} /> <button
) : ( type="button"
children onClick={() => setIsExpanded(false)}
)} className="relative shrink-0 w-6 cursor-pointer group/border"
aria-label="Collapse thinking"
>
<span className="absolute left-0 top-0 bottom-0 w-px bg-border/60 group-hover/border:w-[2px] group-hover/border:bg-foreground/40 transition-all" />
</button>
<div className="text-sm text-muted-foreground pb-2 pt-1">
{hasExpanded ? (
typeof children === "string" ? (
<VanillaMarkdownParser content={children} />
) : (
children
)
) : null}
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
......
import React from "react"; import React from "react";
import { Zap } from "lucide-react"; import { Zap } from "lucide-react";
import { Tooltip, TooltipTrigger, TooltipContent } from "../ui/tooltip"; import { Tooltip, TooltipTrigger, TooltipContent } from "../ui/tooltip";
import { DyadCard, DyadCardHeader } from "./DyadCardPrimitives";
interface DyadTokenSavingsProps { interface DyadTokenSavingsProps {
originalTokens: number; originalTokens: number;
...@@ -12,19 +13,19 @@ export const DyadTokenSavings: React.FC<DyadTokenSavingsProps> = ({ ...@@ -12,19 +13,19 @@ export const DyadTokenSavings: React.FC<DyadTokenSavingsProps> = ({
smartContextTokens, smartContextTokens,
}) => { }) => {
const tokensSaved = originalTokens - smartContextTokens; const tokensSaved = originalTokens - smartContextTokens;
const percentageSaved = Math.round((tokensSaved / originalTokens) * 100); const percentageSaved =
originalTokens > 0 ? Math.round((tokensSaved / originalTokens) * 100) : 0;
return ( return (
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<div className="bg-green-50 dark:bg-green-950 hover:bg-green-100 dark:hover:bg-green-900 rounded-lg px-4 py-2 border border-green-200 dark:border-green-800 my-2 cursor-pointer"> <DyadCard accentColor="green">
<div className="flex items-center gap-2 text-green-700 dark:text-green-300"> <DyadCardHeader icon={<Zap size={15} />} accentColor="green">
<Zap size={16} className="text-green-600 dark:text-green-400" /> <span className="text-xs font-medium text-green-700 dark:text-green-300">
<span className="text-xs font-medium">
Saved {percentageSaved}% of codebase tokens with Smart Context Saved {percentageSaved}% of codebase tokens with Smart Context
</span> </span>
</div> </DyadCardHeader>
</div> </DyadCard>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="top" align="center"> <TooltipContent side="top" align="center">
<div className="text-left"> <div className="text-left">
......
import type React from "react"; import type React from "react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { ScanQrCode } from "lucide-react"; import { ScanQrCode } from "lucide-react";
import { DyadCard, DyadCardHeader, DyadBadge } from "./DyadCardPrimitives";
interface DyadWebCrawlProps { interface DyadWebCrawlProps {
children?: ReactNode; children?: ReactNode;
...@@ -12,16 +13,15 @@ export const DyadWebCrawl: React.FC<DyadWebCrawlProps> = ({ ...@@ -12,16 +13,15 @@ export const DyadWebCrawl: React.FC<DyadWebCrawlProps> = ({
node: _node, node: _node,
}) => { }) => {
return ( return (
<div className="bg-(--background-lightest) rounded-lg px-4 py-2 border my-2"> <DyadCard accentColor="blue">
<div className="flex items-center justify-between"> <DyadCardHeader icon={<ScanQrCode size={15} />} accentColor="blue">
<div className="flex items-center gap-2"> <DyadBadge color="blue">Web Crawl</DyadBadge>
<ScanQrCode size={16} className="text-blue-600" /> </DyadCardHeader>
<div className="text-xs text-blue-600 font-medium">Web Crawl</div> {children && (
<div className="px-3 pb-2 text-sm italic text-muted-foreground">
{children}
</div> </div>
</div> )}
<div className="text-sm italic text-gray-600 dark:text-gray-300 mt-2"> </DyadCard>
{children}
</div>
</div>
); );
}; };
import type React from "react"; import type React from "react";
import { useState, type ReactNode } from "react"; import { useState, type ReactNode } from "react";
import { ChevronDown, ChevronUp, Globe, Loader } from "lucide-react"; import { Globe } from "lucide-react";
import { CustomTagState } from "./stateTypes"; import { CustomTagState } from "./stateTypes";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadExpandIcon,
DyadStateIndicator,
DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadWebSearchProps { interface DyadWebSearchProps {
children?: ReactNode; children?: ReactNode;
...@@ -19,76 +27,46 @@ export const DyadWebSearch: React.FC<DyadWebSearchProps> = ({ ...@@ -19,76 +27,46 @@ export const DyadWebSearch: React.FC<DyadWebSearchProps> = ({
const inProgress = state === "pending"; const inProgress = state === "pending";
return ( return (
<div <DyadCard
className={`bg-(--background-lightest) dark:bg-zinc-900 hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${ state={state}
inProgress ? "border-blue-500" : "border-border" accentColor="blue"
}`} isExpanded={isExpanded}
onClick={() => setIsExpanded(!isExpanded)} onClick={() => setIsExpanded(!isExpanded)}
role="button"
aria-expanded={isExpanded}
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setIsExpanded(!isExpanded);
}
}}
> >
<div className="flex items-center justify-between"> <DyadCardHeader icon={<Globe size={15} />} accentColor="blue">
<div className="flex items-center gap-2"> <DyadBadge color="blue">Web Search</DyadBadge>
<Globe size={16} className="text-blue-600" /> {!isExpanded && query && (
<div className="text-xs text-blue-600 font-medium">Web Search</div> <span className="text-sm text-muted-foreground italic truncate">
{inProgress && ( {query}
<div className="flex items-center text-blue-600 text-xs"> </span>
<Loader size={14} className="mr-1 animate-spin" /> )}
<span>Searching...</span> {inProgress && (
</div> <DyadStateIndicator state="pending" pendingLabel="Searching..." />
)} )}
</div> <div className="ml-auto">
<div className="p-1 text-gray-500"> <DyadExpandIcon isExpanded={isExpanded} />
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
</div> </div>
</div> </DyadCardHeader>
<DyadCardContent isExpanded={isExpanded}>
{/* Collapsed preview - show query */} <div className="text-sm text-muted-foreground space-y-2">
<div
className="text-sm italic text-gray-600 dark:text-gray-300 mt-2 overflow-hidden transition-all duration-300 ease-in-out"
style={{
maxHeight: isExpanded ? "0px" : "3em",
opacity: isExpanded ? 0 : 1,
}}
>
{query}
</div>
{/* Expanded content */}
<div
className="overflow-hidden transition-all duration-300 ease-in-out"
style={{
maxHeight: isExpanded ? "none" : "0px",
opacity: isExpanded ? 1 : 0,
marginTop: isExpanded ? "0.5rem" : "0",
}}
>
<div className="text-sm text-gray-600 dark:text-gray-300 space-y-2">
{query && ( {query && (
<div> <div>
<span className="text-xs font-medium text-gray-500 dark:text-gray-400"> <span className="text-xs font-medium text-muted-foreground">
Query: Query:
</span> </span>
<div className="italic mt-0.5">{query}</div> <div className="italic mt-0.5 text-foreground">{query}</div>
</div> </div>
)} )}
{children && ( {children && (
<div> <div>
<span className="text-xs font-medium text-gray-500 dark:text-gray-400"> <span className="text-xs font-medium text-muted-foreground">
Results: Results:
</span> </span>
<div className="mt-0.5">{children}</div> <div className="mt-0.5 text-foreground">{children}</div>
</div> </div>
)} )}
</div> </div>
</div> </DyadCardContent>
</div> </DyadCard>
); );
}; };
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { ChevronDown, ChevronUp, Globe, Loader } from "lucide-react"; import { Globe } from "lucide-react";
import { VanillaMarkdownParser } from "./DyadMarkdownParser"; import { VanillaMarkdownParser } from "./DyadMarkdownParser";
import { CustomTagState } from "./stateTypes"; import { CustomTagState } from "./stateTypes";
import {
DyadCard,
DyadCardHeader,
DyadBadge,
DyadExpandIcon,
DyadStateIndicator,
DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadWebSearchResultProps { interface DyadWebSearchResultProps {
node?: any; node?: any;
...@@ -16,7 +24,6 @@ export const DyadWebSearchResult: React.FC<DyadWebSearchResultProps> = ({ ...@@ -16,7 +24,6 @@ export const DyadWebSearchResult: React.FC<DyadWebSearchResultProps> = ({
const inProgress = state === "pending"; const inProgress = state === "pending";
const [isExpanded, setIsExpanded] = useState(inProgress); const [isExpanded, setIsExpanded] = useState(inProgress);
// Collapse when transitioning from in-progress to not-in-progress
useEffect(() => { useEffect(() => {
if (!inProgress && isExpanded) { if (!inProgress && isExpanded) {
setIsExpanded(false); setIsExpanded(false);
...@@ -24,55 +31,30 @@ export const DyadWebSearchResult: React.FC<DyadWebSearchResultProps> = ({ ...@@ -24,55 +31,30 @@ export const DyadWebSearchResult: React.FC<DyadWebSearchResultProps> = ({
}, [inProgress]); }, [inProgress]);
return ( return (
<div <DyadCard
className={`relative bg-(--background-lightest) dark:bg-zinc-900 hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${ state={state}
inProgress ? "border-blue-500" : "border-border" accentColor="blue"
}`}
onClick={() => setIsExpanded(!isExpanded)} onClick={() => setIsExpanded(!isExpanded)}
role="button" isExpanded={isExpanded}
aria-expanded={isExpanded}
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setIsExpanded(!isExpanded);
}
}}
> >
{/* Top-left label badge */} <DyadCardHeader icon={<Globe size={15} />} accentColor="blue">
<div <DyadBadge color="blue">Web Search Result</DyadBadge>
className="absolute top-2 left-2 flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold text-blue-600 bg-white dark:bg-zinc-900"
style={{ zIndex: 1 }}
>
<Globe size={16} className="text-blue-600" />
<span>Web Search Result</span>
{inProgress && ( {inProgress && (
<Loader size={14} className="ml-1 text-blue-600 animate-spin" /> <DyadStateIndicator state="pending" pendingLabel="Loading..." />
)} )}
</div> <div className="ml-auto">
<DyadExpandIcon isExpanded={isExpanded} />
{/* Indicator icon */} </div>
<div className="absolute top-2 right-2 p-1 text-gray-500"> </DyadCardHeader>
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />} <DyadCardContent isExpanded={isExpanded}>
</div> <div className="text-sm text-muted-foreground">
{/* Main content with smooth transition */}
<div
className="pt-6 overflow-hidden transition-all duration-300 ease-in-out"
style={{
maxHeight: isExpanded ? "none" : "0px",
opacity: isExpanded ? 1 : 0,
marginBottom: isExpanded ? "0" : "-6px",
}}
>
<div className="px-0 text-sm text-gray-600 dark:text-gray-300">
{typeof children === "string" ? ( {typeof children === "string" ? (
<VanillaMarkdownParser content={children} /> <VanillaMarkdownParser content={children} />
) : ( ) : (
children children
)} )}
</div> </div>
</div> </DyadCardContent>
</div> </DyadCard>
); );
}; };
import type React from "react"; import type React from "react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { useState } from "react"; import { useState } from "react";
import { import { Pencil, Edit, X } from "lucide-react";
ChevronsDownUp,
ChevronsUpDown,
Pencil,
Loader,
CircleX,
Edit,
X,
} from "lucide-react";
import { CodeHighlight } from "./CodeHighlight"; import { CodeHighlight } from "./CodeHighlight";
import { CustomTagState } from "./stateTypes"; import { CustomTagState } from "./stateTypes";
import { FileEditor } from "../preview_panel/FileEditor"; import { FileEditor } from "../preview_panel/FileEditor";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { selectedAppIdAtom } from "@/atoms/appAtoms"; import { selectedAppIdAtom } from "@/atoms/appAtoms";
import {
DyadCard,
DyadCardHeader,
DyadExpandIcon,
DyadStateIndicator,
DyadDescription,
DyadCardContent,
} from "./DyadCardPrimitives";
interface DyadWriteProps { interface DyadWriteProps {
children?: ReactNode; children?: ReactNode;
...@@ -31,7 +31,6 @@ export const DyadWrite: React.FC<DyadWriteProps> = ({ ...@@ -31,7 +31,6 @@ export const DyadWrite: React.FC<DyadWriteProps> = ({
}) => { }) => {
const [isContentVisible, setIsContentVisible] = useState(false); const [isContentVisible, setIsContentVisible] = useState(false);
// Use props directly if provided, otherwise extract from node
const path = pathProp || node?.properties?.path || ""; const path = pathProp || node?.properties?.path || "";
const description = descriptionProp || node?.properties?.description || ""; const description = descriptionProp || node?.properties?.description || "";
const state = node?.properties?.state as CustomTagState; const state = node?.properties?.state as CustomTagState;
...@@ -49,64 +48,56 @@ export const DyadWrite: React.FC<DyadWriteProps> = ({ ...@@ -49,64 +48,56 @@ export const DyadWrite: React.FC<DyadWriteProps> = ({
setIsEditing(true); setIsEditing(true);
setIsContentVisible(true); setIsContentVisible(true);
}; };
// Extract filename from path
const fileName = path ? path.split("/").pop() : ""; const fileName = path ? path.split("/").pop() : "";
return ( return (
<div <DyadCard
className={`bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${ state={state}
inProgress accentColor="blue"
? "border-amber-500"
: aborted
? "border-red-500"
: "border-border"
}`}
onClick={() => setIsContentVisible(!isContentVisible)} onClick={() => setIsContentVisible(!isContentVisible)}
isExpanded={isContentVisible}
> >
<div className="flex items-center justify-between"> <DyadCardHeader icon={<Pencil size={15} />} accentColor="blue">
<div className="flex items-center gap-2"> <div className="min-w-0 truncate">
<Pencil size={16} />
{fileName && ( {fileName && (
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm"> <span className="font-medium text-sm text-foreground truncate block">
{fileName} {fileName}
</span> </span>
)} )}
{inProgress && ( {path && (
<div className="flex items-center text-amber-600 text-xs"> <span className="text-[11px] text-muted-foreground truncate block">
<Loader size={14} className="mr-1 animate-spin" /> {path}
<span>Writing...</span> </span>
</div>
)}
{aborted && (
<div className="flex items-center text-red-600 text-xs">
<CircleX size={14} className="mr-1" />
<span>Did not finish</span>
</div>
)} )}
</div> </div>
<div className="flex items-center"> {inProgress && (
<DyadStateIndicator state="pending" pendingLabel="Writing..." />
)}
{aborted && (
<DyadStateIndicator state="aborted" abortedLabel="Did not finish" />
)}
<div className="ml-auto flex items-center gap-1">
{!inProgress && ( {!inProgress && (
<> <>
{isEditing ? ( {isEditing ? (
<> <button
<button onClick={(e) => {
onClick={(e) => { e.stopPropagation();
e.stopPropagation(); handleCancel();
handleCancel(); }}
}} className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground px-2 py-1 rounded transition-colors cursor-pointer"
className="flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 px-2 py-1 rounded cursor-pointer" >
> <X size={14} />
<X size={14} /> Cancel
Cancel </button>
</button>
</>
) : ( ) : (
<button <button
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
handleEdit(); handleEdit();
}} }}
className="flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 px-2 py-1 rounded cursor-pointer" className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground px-2 py-1 rounded transition-colors cursor-pointer"
> >
<Edit size={14} /> <Edit size={14} />
Edit Edit
...@@ -114,37 +105,24 @@ export const DyadWrite: React.FC<DyadWriteProps> = ({ ...@@ -114,37 +105,24 @@ export const DyadWrite: React.FC<DyadWriteProps> = ({
)} )}
</> </>
)} )}
{isContentVisible ? ( <DyadExpandIcon isExpanded={isContentVisible} />
<ChevronsDownUp
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
) : (
<ChevronsUpDown
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)}
</div> </div>
</div> </DyadCardHeader>
{path && (
<div className="text-xs text-gray-500 dark:text-gray-400 font-medium mb-1">
{path}
</div>
)}
{description && ( {description && (
<div className="text-sm text-gray-600 dark:text-gray-300"> <DyadDescription>
<span className="font-medium">Summary: </span> <span className={!isContentVisible ? "line-clamp-2" : undefined}>
{description} <span className="font-medium">Summary: </span>
</div> {description}
</span>
</DyadDescription>
)} )}
{isContentVisible && ( <DyadCardContent isExpanded={isContentVisible}>
<div <div
className="text-xs cursor-text" className="text-xs cursor-text"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
{isEditing ? ( {isEditing ? (
<div className="h-96 min-h-96 border border-gray-200 dark:border-gray-700 rounded overflow-hidden"> <div className="h-96 min-h-96 border border-border rounded-lg overflow-hidden">
<FileEditor appId={appId ?? null} filePath={path} /> <FileEditor appId={appId ?? null} filePath={path} />
</div> </div>
) : ( ) : (
...@@ -153,7 +131,7 @@ export const DyadWrite: React.FC<DyadWriteProps> = ({ ...@@ -153,7 +131,7 @@ export const DyadWrite: React.FC<DyadWriteProps> = ({
</CodeHighlight> </CodeHighlight>
)} )}
</div> </div>
)} </DyadCardContent>
</div> </DyadCard>
); );
}; };
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论