| banksean | 333aa67 | 2025-07-13 19:49:21 +0000 | [diff] [blame] | 1 | # Modern Component Architecture: SketchTailwindElement |
| 2 | |
| 3 | **IMPORTANT: New component architecture standards for all Sketch web components** |
| 4 | |
| 5 | ## New Component Standard (Use This) |
| 6 | |
| 7 | All new Sketch web components should: |
| 8 | |
| 9 | 1. **Inherit from `SketchTailwindElement`** instead of `LitElement` |
| 10 | 2. **Use Tailwind CSS classes** for styling instead of CSS-in-JS |
| 11 | 3. **Use property-based composition** instead of slot-based composition |
| 12 | 4. **Avoid Shadow DOM** whenever possible |
| 13 | |
| 14 | ### Example of Modern Component: |
| 15 | |
| 16 | ```typescript |
| 17 | import { html } from "lit"; |
| 18 | import { customElement, property } from "lit/decorators.js"; |
| 19 | import { SketchTailwindElement } from "./sketch-tailwind-element"; |
| 20 | import "./sketch-tool-card-base"; |
| 21 | |
| 22 | @customElement("sketch-tool-card-example") |
| 23 | export class SketchToolCardExample extends SketchTailwindElement { |
| 24 | @property() toolCall: ToolCall; |
| 25 | @property() open: boolean; |
| 26 | |
| 27 | render() { |
| 28 | const summaryContent = html`<span class="font-mono text-sm text-gray-700"> |
| 29 | ${this.toolCall?.name} |
| 30 | </span>`; |
| 31 | |
| 32 | const inputContent = html`<div class="p-2 bg-gray-100 rounded"> |
| 33 | <pre class="whitespace-pre-wrap break-words">${this.toolCall?.input}</pre> |
| 34 | </div>`; |
| 35 | |
| 36 | return html`<sketch-tool-card-base |
| 37 | .open=${this.open} |
| 38 | .toolCall=${this.toolCall} |
| 39 | .summaryContent=${summaryContent} |
| 40 | .inputContent=${inputContent} |
| 41 | ></sketch-tool-card-base>`; |
| 42 | } |
| 43 | } |
| 44 | ``` |
| 45 | |
| 46 | ### Key Differences from Legacy Components: |
| 47 | |
| 48 | - **Extends SketchTailwindElement** (not LitElement) |
| 49 | - **Uses sketch-tool-card-base** with property binding (not slots) |
| 50 | - **Tailwind classes** like `font-mono text-sm text-gray-700` (not CSS-in-JS) |
| 51 | - **No static styles** or Shadow DOM |
| 52 | - **Property-based composition** (`.summaryContent=${html\`...\`}`) |
| 53 | |
| 54 | ## Legacy Components (Do NOT Use as Examples) |
| 55 | |
| 56 | Some existing components still use the old architecture and will be migrated: |
| 57 | |
| 58 | - Components that extend `LitElement` directly |
| 59 | - Components that use `static styles = css\`...\`` |
| 60 | - Components that use slot-based composition (`<slot name="summary"></slot>`) |
| 61 | - Components that rely on Shadow DOM |
| 62 | |
| 63 | **Do not use these legacy patterns for new components.** They are being phased out. |
| 64 | |
| 65 | ## Migration Status |
| 66 | |
| 67 | As of the latest migration: |
| 68 | |
| 69 | - ✅ All tool card components now use SketchTailwindElement |
| 70 | - ✅ Complete Shadow DOM elimination in tool card hierarchy |
| 71 | - ✅ Consistent Tailwind CSS styling approach |
| 72 | - 🔄 Other components will be migrated over time |
| 73 | |
| 74 | --- |
| 75 | |
| Sean McCullough | 848572d | 2025-05-13 22:27:42 +0000 | [diff] [blame] | 76 | # Component Architecture: sketch-tool-card and Related Components |
| 77 | |
| 78 | This document explains the relationship between LitElement subclasses in webui/src/web-components/ and the sketch-tool-card custom element, focusing on their containment relationship and CSS styling effects. |
| 79 | |
| 80 | ## Containment Relationship |
| 81 | |
| 82 | The component hierarchy and containment relationship is structured as follows: |
| 83 | |
| 84 | 1. **sketch-app-shell** (the main application container) |
| 85 | - Contains **sketch-timeline** (for displaying conversation history) |
| 86 | - Contains **sketch-timeline-message** (for individual messages) |
| 87 | - Contains **sketch-tool-calls** (collection of tool calls) |
| 88 | - Contains specific tool card components like: |
| 89 | - **sketch-tool-card-bash** |
| 90 | - **sketch-tool-card-think** |
| 91 | - **sketch-tool-card-codereview** |
| 92 | - **sketch-tool-card-done** |
| 93 | - **sketch-tool-card-patch** |
| 94 | - **sketch-tool-card-take-screenshot** |
| Josh Bleecher Snyder | 19969a9 | 2025-06-05 14:34:02 -0700 | [diff] [blame] | 95 | - **sketch-tool-card-set-slug** |
| 96 | - **sketch-tool-card-commit-message-style** |
| Sean McCullough | 848572d | 2025-05-13 22:27:42 +0000 | [diff] [blame] | 97 | - **sketch-tool-card-multiple-choice** |
| 98 | - **sketch-tool-card-generic** (fallback for unknown tools) |
| 99 | - All of these specialized components **contain** or **compose with** the base **sketch-tool-card** |
| 100 | |
| 101 | The key aspect is that the specialized tool card components do not inherit from `SketchToolCard` in a class hierarchy sense. Instead, they **use composition** by embedding a `<sketch-tool-card>` element within their render method and passing data to it. |
| 102 | |
| 103 | For example, from `sketch-tool-card-bash.ts`: |
| 104 | |
| 105 | ```typescript |
| 106 | render() { |
| 107 | return html` <sketch-tool-card |
| 108 | .open=${this.open} |
| 109 | .toolCall=${this.toolCall} |
| 110 | > |
| 111 | <span slot="summary" class="summary-text">...</span> |
| 112 | <div slot="input" class="input">...</div> |
| 113 | <div slot="result" class="result">...</div> |
| 114 | </sketch-tool-card>`; |
| 115 | } |
| 116 | ``` |
| 117 | |
| 118 | ## CSS Styling and Effects |
| 119 | |
| 120 | Regarding how CSS rules defined in sketch-tool-card affect elements that contain it: |
| 121 | |
| 122 | 1. **Shadow DOM Encapsulation**: |
| Philip Zeyliger | 2973271 | 2025-06-27 09:57:12 -0700 | [diff] [blame] | 123 | |
| Sean McCullough | 848572d | 2025-05-13 22:27:42 +0000 | [diff] [blame] | 124 | - Each Web Component has its own Shadow DOM, which encapsulates its styles |
| 125 | - Styles defined in `sketch-tool-card` apply only within its shadow DOM, not to parent components |
| 126 | |
| 127 | 2. **Slot Content Styling**: |
| Philip Zeyliger | 2973271 | 2025-06-27 09:57:12 -0700 | [diff] [blame] | 128 | |
| Sean McCullough | 848572d | 2025-05-13 22:27:42 +0000 | [diff] [blame] | 129 | - The base `sketch-tool-card` defines three slots: "summary", "input", and "result" |
| 130 | - Specialized tool cards provide content for these slots |
| 131 | - The base component can style the slot containers, but cannot directly style the slotted content |
| 132 | |
| 133 | 3. **Style Inheritance and Sharing**: |
| Philip Zeyliger | 2973271 | 2025-06-27 09:57:12 -0700 | [diff] [blame] | 134 | |
| Sean McCullough | 848572d | 2025-05-13 22:27:42 +0000 | [diff] [blame] | 135 | - The code uses a `commonStyles` constant that is shared across some components |
| 136 | - These common styles ensure consistent styling for elements like pre, code blocks |
| 137 | - Each specialized component adds its own unique styles as needed |
| 138 | |
| 139 | 4. **Parent CSS Targeting**: |
| Philip Zeyliger | 2973271 | 2025-06-27 09:57:12 -0700 | [diff] [blame] | 140 | |
| Sean McCullough | 848572d | 2025-05-13 22:27:42 +0000 | [diff] [blame] | 141 | - In `sketch-timeline-message.ts`, there are styles that target the tool components using the `::slotted()` pseudo-element: |
| 142 | |
| 143 | ```css |
| 144 | ::slotted(sketch-tool-calls) { |
| 145 | max-width: 100%; |
| 146 | width: 100%; |
| 147 | overflow-wrap: break-word; |
| 148 | word-break: break-word; |
| 149 | } |
| 150 | ``` |
| Philip Zeyliger | b01b150 | 2025-06-26 21:20:12 -0700 | [diff] [blame] | 151 | |
| Sean McCullough | 848572d | 2025-05-13 22:27:42 +0000 | [diff] [blame] | 152 | - This allows parent components to influence the layout of slotted components while preserving Shadow DOM encapsulation |
| 153 | |
| 154 | 5. **Host Element Styling**: |
| Philip Zeyliger | 2973271 | 2025-06-27 09:57:12 -0700 | [diff] [blame] | 155 | |
| Sean McCullough | 848572d | 2025-05-13 22:27:42 +0000 | [diff] [blame] | 156 | - The `:host` selector is used in sketch-tool-card for styling the component itself: |
| banksean | 9aa141a | 2025-06-27 05:02:24 +0000 | [diff] [blame] | 157 | |
| Sean McCullough | 848572d | 2025-05-13 22:27:42 +0000 | [diff] [blame] | 158 | ```css |
| 159 | :host { |
| 160 | display: block; |
| 161 | max-width: 100%; |
| 162 | width: 100%; |
| 163 | box-sizing: border-box; |
| 164 | overflow: hidden; |
| 165 | } |
| 166 | ``` |
| Philip Zeyliger | b01b150 | 2025-06-26 21:20:12 -0700 | [diff] [blame] | 167 | |
| Sean McCullough | 848572d | 2025-05-13 22:27:42 +0000 | [diff] [blame] | 168 | - This affects how the component is displayed in its parent context |
| 169 | |
| 170 | In summary, the architecture uses composition rather than inheritance, with specialized tool cards wrapping the base sketch-tool-card component and filling its slots with custom content. The CSS styling is carefully managed through Shadow DOM, with some targeted styling using ::slotted selectors to ensure proper layout and appearance throughout the component hierarchy. |
| Philip Zeyliger | 3321939 | 2025-05-14 20:43:24 -0700 | [diff] [blame] | 171 | |
| 172 | # API URLs Must Be Relative |
| 173 | |
| 174 | When making fetch requests to backend APIs in Sketch, all URLs **must be relative** without leading slashes. The base URL for Sketch varies depending on how it is deployed and run: |
| 175 | |
| 176 | ```javascript |
| 177 | // CORRECT - use relative paths without leading slash |
| 178 | const response = await fetch(`git/rawdiff?commit=${commit}`); |
| 179 | const data = await fetch(`git/show?hash=${hash}`); |
| 180 | |
| 181 | // INCORRECT - do not use absolute paths with leading slash |
| 182 | const response = await fetch(`/git/rawdiff?commit=${commit}`); // WRONG! |
| 183 | const data = await fetch(`/git/show?hash=${hash}`); // WRONG! |
| 184 | ``` |
| 185 | |
| 186 | The reason for this requirement is that Sketch may be deployed: |
| 187 | |
| 188 | 1. At the root path of a domain |
| 189 | 2. In a subdirectory |
| 190 | 3. Behind a proxy with a path prefix |
| 191 | 4. In different environments (dev, production) |
| 192 | |
| 193 | Using relative paths ensures that requests are correctly routed regardless of the deployment configuration. The browser will resolve the URLs relative to the current page location. |