Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
1cf9f6a
refactor(ui): introduce groupParts pure helper for session-turn
Astro-Han May 12, 2026
bb381ed
refactor(ui): introduce TrowBlock reducer for session-turn
Astro-Han May 12, 2026
a70e960
style(ui): align shimmer duration to W1 lock (1200→1800ms)
Astro-Han May 12, 2026
53a4cea
feat(ui): register fork icon placeholder for session agent toolbar
Astro-Han May 12, 2026
722d9fa
feat(ui): add shared AttachmentChip primitive
Astro-Han May 12, 2026
3911332
feat(ui): add session-turn user bubble for slice 11b.1
Astro-Han May 12, 2026
e421774
feat(ui): add session-turn system event for slice 11b.1
Astro-Han May 12, 2026
55d77ae
feat(ui): add JumpToBottom floating button for slice 11b.1
Astro-Han May 12, 2026
421841a
feat(ui): add session-turn agent round for slice 11b.1
Astro-Han May 12, 2026
b5a995c
feat(ui): fill TrowBlock render shell for slice 11b.1
Astro-Han May 12, 2026
9594e0f
docs(ui): annotate Phase 5 token deferrals for slice 11b.1
Astro-Han May 12, 2026
548fa26
feat(ui): add SessionTurnV2 hybrid opt-in shell for slice 11b.1
Astro-Han May 12, 2026
de6a321
fix(ui): make TrowBlock body reactive across streaming tool updates
Astro-Han May 12, 2026
0fbee29
revert(ui): drop SessionTurnV2 hybrid opt-in shell for slice 11b.1
Astro-Han May 12, 2026
20ccbb1
feat(ui): register --fg-secondary and --icon-chev tokens for slice 11b.1
Astro-Han May 12, 2026
e335a1a
refactor(ui): extract message-part types to dedicated module
Astro-Han May 12, 2026
892ef1a
refactor(ui): extract message-part registry to dedicated module
Astro-Han May 12, 2026
5401c26
refactor(ui): extract paced markdown helpers to dedicated module
Astro-Han May 12, 2026
610e2db
refactor(ui): extract tool-info helpers to dedicated module
Astro-Han May 12, 2026
cad41ef
refactor(ui): extract legacy assistant grouping helpers
Astro-Han May 12, 2026
c3518eb
refactor(ui): extract AssistantMessageDisplay + AssistantParts + Cont…
Astro-Han May 12, 2026
eef95b8
refactor(ui): extract UserMessageDisplay to dedicated module
Astro-Han May 12, 2026
1c01839
refactor(ui): extract tool dispatcher + display chrome to dedicated m…
Astro-Han May 12, 2026
78e9506
refactor(ui): extract MessageDivider + text/reasoning/compaction rend…
Astro-Han May 12, 2026
36100de
refactor(ui): extract lightweight tool renderers to tools-basic module
Astro-Han May 12, 2026
d44e0e4
refactor(ui): extract task/agent tool renderer to tools-agent module
Astro-Han May 12, 2026
1b43dac
refactor(ui): extract bash tool renderer to tools-shell module
Astro-Han May 12, 2026
e82bf8f
refactor(ui): extract file-modifying tools + slim message-part aggreg…
Astro-Han May 12, 2026
c87cb47
refactor(app): extract 5 helper modules from message-timeline
Astro-Han May 12, 2026
e4b21bd
refactor(app): finish message-timeline split + drop dead title editor
Astro-Han May 12, 2026
ade42fe
refactor(ui): extract turn-changes + diffs lists + helpers from sessi…
Astro-Han May 12, 2026
d81e0bf
feat(ui-i18n): add slice 11b.1 W1 surface keys
Astro-Han May 12, 2026
9a1ec65
feat(app/session): mount JumpToBottom leaf in message-timeline
Astro-Han May 12, 2026
116d542
feat(app/session): wire fork action for W1 agent toolbar
Astro-Han May 12, 2026
cb90f6b
feat(session-turn): mount W1 leaf components on the default user path
Astro-Han May 12, 2026
e177559
test(e2e/session): add W1 surface specs covering E1-E16 golden paths
Astro-Han May 12, 2026
77f8e8c
fix(session-turn): align W1 bubble + agent toolbar per AstroHan W1 re…
Astro-Han May 13, 2026
4457e73
fix(auto-scroll): hold ResizeObserver auto-snap after upward wheel
Astro-Han May 13, 2026
add81e2
fix(session-turn): wrap W1 toolbar buttons in Tooltip for hover hints
Astro-Han May 13, 2026
c5a030f
fix(session-turn): selection regression + trow chev direction + neste…
Astro-Han May 13, 2026
a4b44e2
fix(session-turn): lift dark user bubble fill so the shell stays visible
Astro-Han May 13, 2026
6b62f8b
Revert "fix(session-turn): lift dark user bubble fill so the shell st…
Astro-Han May 13, 2026
d8addf2
fix(session-turn): give dark user bubble a hairline ring for shape
Astro-Han May 13, 2026
e2e5def
fix(session-timeline): close P0 #6 scroll-up-snaps-back via 3 minimal…
Astro-Han May 13, 2026
90e3903
fix(session-turn): scope L417 frame + caption typography to per-tool …
Astro-Han May 13, 2026
186d5f8
fix(session-turn): give light user bubble a hairline ring too
Astro-Han May 13, 2026
55893a9
fix(session-turn): trow visual fixes — drop per-tool frame, fix chev
Astro-Han May 13, 2026
b48a860
fix(session-turn): tighten "Thinking…" to pre-first-visible-output only
Astro-Han May 13, 2026
8ac986b
test(app): tag W1 regression smoke gate
Astro-Han May 13, 2026
8ef83fa
fix(session-turn): trow expand/collapse animation + tighter spacing +…
Astro-Han May 13, 2026
83d1dc7
fix(session-turn): wire showReasoningSummaries to AgentRound + trunca…
Astro-Han May 13, 2026
9943552
fix(session-turn): drop W1-broken shell/edit tool defaultOpen toggles
Astro-Han May 13, 2026
b2ee6cb
fix(session-timeline): surface trow toggle as a layout_interaction in…
Astro-Han May 13, 2026
47a4760
fix(session): unify timeline scroll ownership
Astro-Han May 13, 2026
9e071d2
style(session-turn): tighten trow result body row stride
Astro-Han May 13, 2026
969f6a9
perf(ui): render streaming markdown per-block so flushes only diff th…
Astro-Han May 13, 2026
f7646cb
perf(app): coalesce part deltas across status events
Astro-Han May 13, 2026
61e7a0f
perf(app): throttle message part delta flushes
Astro-Han May 13, 2026
aed3994
perf(ui): suppress streaming-period animations inside the active agen…
Astro-Han May 13, 2026
f49fd6d
perf(app): unmount the active terminal when the side panel hides it
Astro-Han May 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions packages/ui/src/components/attachment-chip.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* AttachmentChip — slice 11b.1.
*
* One spec across the product (DESIGN.md L444). The composer dock and the
* user-message bubble both render this primitive; the only difference is
* the close button, gated by `[data-removable]`.
*
* Geometry mirrors the W1 visual preview `.mf-attach` / `.mf-attach-img`
* stamps in docs/design/preview/message-flow.html. Class names here use
* the data-component / data-slot convention so they match every other
* shared primitive in packages/ui.
*/

[data-component="attachment-chip"] {
flex-shrink: 0;
max-width: 280px;
position: relative;
box-sizing: border-box;
}

/* File chip — height 64, gap 12, radius-md, 1px hairline, transparent bg
* so the chip blends with whichever surface owns the row (cream in
* the bubble, white in the composer dock). */
[data-component="attachment-chip"][data-kind="file"] {
display: inline-flex;
align-items: center;
gap: 12px;
height: 64px;
padding: 8px 12px;
border-radius: var(--radius-md);
border: 1px solid var(--border-weaker);
background: transparent;
line-height: 1.4;
}

[data-component="attachment-chip"][data-kind="file"] [data-slot="attachment-chip-icon"] {
width: 48px;
height: 48px;
border-radius: var(--radius-sm);
background: var(--surface-sunken);
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
color: var(--fg-base);
}

[data-component="attachment-chip"][data-kind="file"] [data-slot="attachment-chip-icon"] [data-icon] {
width: 32px;
height: 32px;
}

[data-component="attachment-chip"][data-kind="file"] [data-slot="attachment-chip-body"] {
display: flex;
flex-direction: column;
min-width: 0;
gap: 2px;
flex: 1;
}

[data-component="attachment-chip"][data-kind="file"] [data-slot="attachment-chip-name"] {
font: var(--type-body);
color: var(--fg-strong);
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

[data-component="attachment-chip"][data-kind="file"] [data-slot="attachment-chip-ext"] {
font: var(--type-caption);
color: var(--fg-weak);
line-height: 1.3;
text-transform: uppercase;
letter-spacing: 0.5px;
}

/* Image chip — square 64×64 thumbnail, same border + radius as file chip
* shell so the two read as one family. `object-fit: cover` keeps the
* thumbnail filling the box regardless of source aspect ratio. */
[data-component="attachment-chip"][data-kind="image"] {
width: 64px;
height: 64px;
border-radius: var(--radius-md);
border: 1px solid var(--border-weaker);
background: var(--surface-sunken);
overflow: hidden;
}

[data-component="attachment-chip"][data-kind="image"] [data-slot="attachment-chip-image"] {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}

/* Remove button — 24 circle, fg-strong fill, bg-base glyph, floats at
* top right -6/-6. Only rendered when `[data-removable]` is present, so
* the bubble's archived chip never shows it (W1 L1088). */
[data-component="attachment-chip"] [data-slot="attachment-chip-remove"] {
position: absolute;
top: -6px;
right: -6px;
width: 24px;
height: 24px;
border-radius: 9999px;
background: var(--fg-strong);
color: var(--bg-base);
border: 0;
padding: 0;
cursor: default;
display: inline-flex;
align-items: center;
justify-content: center;
}
Comment on lines +100 to +115
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a pointer cursor for the removable close button.

The remove control is clickable, but cursor: default makes it appear non-interactive.

Suggested patch
 [data-component="attachment-chip"] [data-slot="attachment-chip-remove"] {
   position: absolute;
   top: -6px;
   right: -6px;
@@
-  cursor: default;
+  cursor: pointer;
   display: inline-flex;
   align-items: center;
   justify-content: center;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[data-component="attachment-chip"] [data-slot="attachment-chip-remove"] {
position: absolute;
top: -6px;
right: -6px;
width: 24px;
height: 24px;
border-radius: 9999px;
background: var(--fg-strong);
color: var(--bg-base);
border: 0;
padding: 0;
cursor: default;
display: inline-flex;
align-items: center;
justify-content: center;
}
[data-component="attachment-chip"] [data-slot="attachment-chip-remove"] {
position: absolute;
top: -6px;
right: -6px;
width: 24px;
height: 24px;
border-radius: 9999px;
background: var(--fg-strong);
color: var(--bg-base);
border: 0;
padding: 0;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/attachment-chip.css` around lines 100 - 115, The
remove button styled by the selector [data-component="attachment-chip"]
[data-slot="attachment-chip-remove"] is interactive but uses cursor: default;
change its cursor to pointer so it appears clickable. Locate the rule for the
remove control (selector with data-component="attachment-chip" and
data-slot="attachment-chip-remove") and replace cursor: default with cursor:
pointer (or add cursor: pointer) while leaving the other layout/visual
properties intact.


[data-component="attachment-chip"] [data-slot="attachment-chip-remove"] [data-icon] {
width: 14px;
height: 14px;
}

[data-component="attachment-chip"] [data-slot="attachment-chip-remove"]:focus-visible {
outline: none;
box-shadow: 0 0 0 1px var(--brand-primary), 0 0 0 3px rgba(255, 89, 16, 0.2);
}
50 changes: 50 additions & 0 deletions packages/ui/src/components/attachment-chip.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { expect, test } from "bun:test"
import { readFileSync } from "node:fs"

// Source-grep style tests, matching the repo convention used by
// message-part-stale.test.ts. The full behavioural / removable-vs-read-only
// matrix is covered by session-turn-user-bubble.test.tsx (Phase 2c) and the
// dev:desktop D2 manual check. Tests here pin the structural invariants the
// design doc §3.7 calls out.

const source = readFileSync(new URL("./attachment-chip.tsx", import.meta.url), "utf8")
const css = readFileSync(new URL("./attachment-chip.css", import.meta.url), "utf8")

test("attachment-chip is the shared primitive (one component, file + image)", () => {
// One component name, two visual kinds via data-kind, gated by removable.
expect(source).toMatch(/export function AttachmentChip\(/)
expect(source).toMatch(/data-kind="file"/)
expect(source).toMatch(/data-kind="image"/)
})

test("attachment-chip × close button is gated by `removable` prop", () => {
// The Show wrappers around the remove button must use props.removable
// (DESIGN.md L460, message-flow.html L1088: bubble never renders ×).
expect(source).toMatch(/<Show when=\{props\.removable\}>/)
// And the close button glyph must use the existing chrome icon.
expect(source).toMatch(/<Icon name="close" \/>/)
})

test("attachment-chip file-kind exposes name + ext slots (W1 right column)", () => {
expect(source).toMatch(/data-slot="attachment-chip-name"/)
expect(source).toMatch(/data-slot="attachment-chip-ext"/)
})

test("CSS geometry matches W1 lock: file chip h64 / radius-md / max-width 280", () => {
// Pin the visual contract — these three values are the ones the W1
// preview lock-stamp calls out by name.
expect(css).toMatch(/\[data-component="attachment-chip"\][^{}]*\{[^}]*max-width:\s*280px/)
expect(css).toMatch(/\[data-kind="file"\][^{}]*\{[^}]*height:\s*64px/)
expect(css).toMatch(/\[data-kind="file"\][^{}]*\{[^}]*border-radius:\s*var\(--radius-md\)/)
})

test("CSS image-kind: 64×64 square + radius-md + object-fit cover", () => {
expect(css).toMatch(/\[data-kind="image"\][^{}]*\{[^}]*width:\s*64px[^}]*height:\s*64px/)
expect(css).toMatch(/\[data-kind="image"\][^}]*\}\s*\n\s*\[data-component="attachment-chip"\]\[data-kind="image"\][^{}]*\[data-slot="attachment-chip-image"\][^{}]*\{[^}]*object-fit:\s*cover/)
})

test("CSS remove button floats top-right with 24-circle + brand-color hairline ring on focus", () => {
expect(css).toMatch(/\[data-slot="attachment-chip-remove"\][^{}]*\{[^}]*top:\s*-6px[^}]*right:\s*-6px/)
expect(css).toMatch(/\[data-slot="attachment-chip-remove"\][^{}]*\{[^}]*width:\s*24px[^}]*height:\s*24px/)
expect(css).toMatch(/\[data-slot="attachment-chip-remove"\]:focus-visible[^{}]*\{[^}]*var\(--brand-primary\)/)
})
125 changes: 125 additions & 0 deletions packages/ui/src/components/attachment-chip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { Show, splitProps, type ComponentProps } from "solid-js"
import { Icon, type IconName } from "./icon"
import "./attachment-chip.css"

/**
* Shared attachment chip for slice 11b.1. Replaces the ad-hoc chip
* implementations the user-message bubble and (eventually) the composer
* dock used to maintain side-by-side, ensuring "one attachment spec across
* the product" per DESIGN.md L444.
*
* The single shape difference between bubble (archived state) and dock
* (editor state) is the close button — controlled by the `removable` prop.
* All other geometry (height 64 for files, 64×64 for images, radius-md,
* 1px `--border-weaker`, transparent background, max-width 280) is
* uniform.
*
* The chip is read-only display surface: it does NOT participate in the
* focus / Tab order (DESIGN.md §a11y for attachments; §6.9 confirms).
* When `removable` is true, the × button itself does enter the focus
* order so keyboard users can dismiss attachments.
*/
export interface AttachmentChipProps {
/** Kind of attachment — drives the file vs image visual branch. */
kind: "file" | "image"
/** Display name for files (e.g. `report.pdf`). Ignored for images. */
name?: string
/** Extension label shown under the filename. Defaults to the filename's tail. */
ext?: string
/** Image preview URL for `kind="image"`. */
previewUrl?: string
/** Alt text for the image; falls back to `name` then a default string. */
alt?: string
/** Icon shown inside the 48×48 square slot for files. Defaults to `doc-processing`. */
icon?: IconName
/** When true, render the × close button (composer editor state). */
removable?: boolean
/** Click handler for the × button. Only fires when `removable` is true. */
onRemove?: (event: MouseEvent) => void
/** Accessible label for the remove button (i18n-resolved by caller). */
removeLabel?: string
}

function defaultExtFromName(name?: string): string | undefined {
if (!name) return undefined
const dot = name.lastIndexOf(".")
if (dot < 0 || dot === name.length - 1) return undefined
return name.slice(dot + 1)
}

export function AttachmentChip(rawProps: AttachmentChipProps & Omit<ComponentProps<"div">, keyof AttachmentChipProps>) {
const [props, rest] = splitProps(rawProps, [
"kind",
"name",
"ext",
"previewUrl",
"alt",
"icon",
"removable",
"onRemove",
"removeLabel",
])

const resolvedExt = () => props.ext ?? defaultExtFromName(props.name)

return (
<Show
when={props.kind === "image"}
Comment on lines +66 to +67
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard the image branch when previewUrl is missing.

If kind="image" arrives without previewUrl, the component renders a broken/empty image slot.

Suggested patch
     <Show
-      when={props.kind === "image"}
+      when={props.kind === "image" && !!props.previewUrl}
       fallback={

Also applies to: 107-111

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/attachment-chip.tsx` around lines 66 - 67, The
image branch renders even when previewUrl is absent; update the conditional
guards on the Show components that check props.kind === "image" to also require
props.previewUrl (e.g., change to props.kind === "image" && props.previewUrl) so
the image slot only renders when a previewUrl exists; apply the same guard to
the second Show block that handles the image preview (the block around the logic
currently at lines ~107-111) and ensure any <img> usage uses props.previewUrl
safely.

fallback={
<div
data-component="attachment-chip"
data-kind="file"
data-removable={props.removable || undefined}
{...rest}
>
<span data-slot="attachment-chip-icon">
<Icon name={props.icon ?? "doc-processing"} />
</span>
<span data-slot="attachment-chip-body">
<Show when={props.name}>
<span data-slot="attachment-chip-name" title={props.name}>
{props.name}
</span>
</Show>
<Show when={resolvedExt()}>
<span data-slot="attachment-chip-ext">{resolvedExt()}</span>
</Show>
</span>
<Show when={props.removable}>
<button
type="button"
data-slot="attachment-chip-remove"
aria-label={props.removeLabel}
onClick={(event) => props.onRemove?.(event)}
>
<Icon name="close" />
</button>
</Show>
Comment on lines +88 to +97
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ensure the remove button always has an accessible name.

When removable is true and removeLabel is omitted, the button can be rendered without a usable accessible label.

Suggested patch
 export function AttachmentChip(rawProps: AttachmentChipProps & Omit<ComponentProps<"div">, keyof AttachmentChipProps>) {
@@
   const resolvedExt = () => props.ext ?? defaultExtFromName(props.name)
+  const resolvedRemoveLabel = () => props.removeLabel ?? "Remove attachment"
@@
             <button
               type="button"
               data-slot="attachment-chip-remove"
-              aria-label={props.removeLabel}
+              aria-label={resolvedRemoveLabel()}
               onClick={(event) => props.onRemove?.(event)}
             >
@@
           <button
             type="button"
             data-slot="attachment-chip-remove"
-            aria-label={props.removeLabel}
+            aria-label={resolvedRemoveLabel()}
             onClick={(event) => props.onRemove?.(event)}
           >

Also applies to: 112-121

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/attachment-chip.tsx` around lines 88 - 97, When
rendering the remove button in the attachment chip, ensure it always has an
accessible name by providing a default label when props.removeLabel is missing:
change the aria-label usage on the button in the removable branch to use
props.removeLabel ?? 'Remove attachment' (and do the same for the second
occurrence around the Icon branch at the other location). Also mark the Icon
component as decorative (e.g., ensure Icon is aria-hidden or otherwise not
exposing an extra name) so the button's aria-label is the actual accessible
name, and keep onRemove as the click handler.

</div>
}
>
<div
data-component="attachment-chip"
data-kind="image"
data-removable={props.removable || undefined}
{...rest}
>
<img
data-slot="attachment-chip-image"
src={props.previewUrl}
alt={props.alt ?? props.name ?? "attachment"}
/>
<Show when={props.removable}>
<button
type="button"
data-slot="attachment-chip-remove"
aria-label={props.removeLabel}
onClick={(event) => props.onRemove?.(event)}
>
<Icon name="close" />
</button>
</Show>
</div>
</Show>
)
}
6 changes: 6 additions & 0 deletions packages/ui/src/components/icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ export const icons = {
"bar-chart": `<g transform="translate(1.000 1.959) scale(0.091371)"><path d="M0,0 L31,0 L37,3 L40,7 L40,166 L53,166 L56,168 L57,172 L53,176 L-137,176 L-140,173 L-139,168 L-136,166 L-123,166 L-123,112 L-120,107 L-116,105 L-90,105 L-85,108 L-82,112 L-82,166 L-67,166 L-67,54 L-62,48 L-59,47 L-33,47 L-28,50 L-25,55 L-25,166 L-10,166 L-10,9 L-7,3 Z M0,10 L0,166 L30,166 L30,10 Z M-56,56 L-57,57 L-57,166 L-35,166 L-35,57 L-36,56 Z M-113,114 L-114,115 L-114,166 L-92,166 L-92,115 L-93,114 Z " transform="translate(140.000 0.000)" fill="currentColor"/></g>`,
"brain": `<g transform="translate(1.051 1.000) scale(0.101124)"><path d="M0,0 L7,0 L17,3 L26,9 L27,12 L30,11 L35,6 L45,1 L50,0 L63,1 L73,6 L78,10 L84,20 L86,32 L91,33 L99,38 L106,46 L110,55 L111,60 L111,69 L110,77 L116,89 L117,93 L117,106 L113,117 L105,127 L97,134 L93,149 L87,158 L80,166 L75,171 L69,175 L59,178 L49,178 L38,174 L30,167 L28,166 L23,171 L15,176 L8,178 L-3,178 L-13,174 L-19,170 L-32,156 L-37,146 L-39,135 L-51,124 L-57,114 L-60,105 L-60,95 L-56,82 L-53,77 L-54,69 L-54,60 L-51,50 L-45,41 L-38,35 L-32,32 L-29,32 L-27,20 L-21,11 L-14,5 L-6,1 Z M-3,10 L-11,14 L-17,21 L-19,25 L-19,32 L-16,36 L-17,40 L-21,41 L-26,40 L-33,43 L-40,49 L-44,57 L-44,71 L-42,76 L-43,80 L-48,86 L-51,96 L-50,107 L-46,115 L-44,115 L-39,105 L-36,102 L-29,101 L-29,107 L-35,114 L-36,116 L-36,124 L-31,129 L-24,130 L-17,124 L-12,124 L-11,129 L-16,134 L-21,140 L-21,151 L-17,159 L-9,166 L-4,168 L10,168 L18,163 L23,157 L24,153 L24,24 L21,18 L13,11 L10,10 Z M52,9 L43,12 L36,18 L33,24 L33,153 L37,161 L43,166 L53,169 L64,167 L70,163 L76,156 L78,152 L78,140 L75,135 L68,130 L68,124 L75,125 L81,130 L88,129 L93,124 L93,117 L89,110 L86,107 L87,102 L90,101 L96,105 L100,111 L101,115 L104,114 L107,108 L108,103 L108,96 L105,86 L99,78 L102,64 L100,54 L94,46 L87,41 L80,40 L75,41 L74,40 L74,35 L76,33 L76,25 L72,18 L65,12 L61,10 Z " transform="translate(60.000 0.000)" fill="currentColor"/><path d="M0,0 L6,1 L14,6 L17,9 L17,14 L15,16 L10,16 L4,11 L-2,8 L-3,7 L-3,2 Z " transform="translate(52.000 76.000)" fill="currentColor"/><path d="M0,0 L6,1 L7,6 L5,9 L-4,13 L-7,16 L-13,16 L-14,9 L-7,3 Z " transform="translate(122.000 76.000)" fill="currentColor"/></g>`,
"branch": `<g transform="translate(1.000 1.233) scale(0.093264)"><path d="M0,0 L11,0 L21,5 L28,13 L31,22 L31,29 L28,38 L21,46 L13,50 L10,50 L11,104 L18,97 L30,91 L34,90 L90,89 L107,89 L118,88 L130,82 L136,75 L140,66 L141,61 L141,51 L131,46 L124,39 L121,32 L121,19 L126,9 L133,3 L140,0 L152,0 L161,4 L167,9 L171,16 L172,20 L172,31 L169,38 L162,46 L152,51 L151,66 L147,77 L141,85 L134,92 L121,98 L116,99 L37,100 L26,104 L18,111 L12,123 L10,137 L17,139 L27,148 L31,159 L30,169 L26,177 L21,182 L15,186 L3,188 L-7,185 L-15,179 L-20,170 L-21,166 L-21,158 L-17,148 L-12,142 L-2,137 L0,137 L0,50 L-6,49 L-12,45 L-18,38 L-21,30 L-21,21 L-17,11 L-10,4 Z M3,9 L-4,12 L-9,17 L-11,21 L-11,30 L-6,38 L-1,41 L10,41 L15,38 L20,32 L21,29 L21,22 L17,14 L11,10 Z M146,9 L137,12 L132,18 L130,27 L133,35 L139,40 L141,41 L151,41 L157,38 L161,33 L162,31 L162,20 L158,14 L152,10 Z M2,146 L-6,150 L-11,158 L-11,166 L-8,172 L-2,177 L2,178 L9,178 L16,174 L20,169 L21,159 L17,151 L11,147 Z " transform="translate(21.000 0.000)" fill="currentColor"/></g>`,
// Slice 11b.1 placeholder. The W1 preview lock-stamp notes Fork has no
// dedicated glyph yet — `branch` is the agreed visual stand-in until
// imagegen Sheet 15's candidate is picked by Astro-Han. Keeping the same
// path data here means the agent-toolbar Fork button renders identically
// to the preview today and a one-line SVG swap finalises the icon later.
"fork": `<g transform="translate(1.000 1.233) scale(0.093264)"><path d="M0,0 L11,0 L21,5 L28,13 L31,22 L31,29 L28,38 L21,46 L13,50 L10,50 L11,104 L18,97 L30,91 L34,90 L90,89 L107,89 L118,88 L130,82 L136,75 L140,66 L141,61 L141,51 L131,46 L124,39 L121,32 L121,19 L126,9 L133,3 L140,0 L152,0 L161,4 L167,9 L171,16 L172,20 L172,31 L169,38 L162,46 L152,51 L151,66 L147,77 L141,85 L134,92 L121,98 L116,99 L37,100 L26,104 L18,111 L12,123 L10,137 L17,139 L27,148 L31,159 L30,169 L26,177 L21,182 L15,186 L3,188 L-7,185 L-15,179 L-20,170 L-21,166 L-21,158 L-17,148 L-12,142 L-2,137 L0,137 L0,50 L-6,49 L-12,45 L-18,38 L-21,30 L-21,21 L-17,11 L-10,4 Z M3,9 L-4,12 L-9,17 L-11,21 L-11,30 L-6,38 L-1,41 L10,41 L15,38 L20,32 L21,29 L21,22 L17,14 L11,10 Z M146,9 L137,12 L132,18 L130,27 L133,35 L139,40 L141,41 L151,41 L157,38 L161,33 L162,31 L162,20 L158,14 L152,10 Z M2,146 L-6,150 L-11,158 L-11,166 L-8,172 L-2,177 L2,178 L9,178 L16,174 L20,169 L21,159 L17,151 L11,147 Z " transform="translate(21.000 0.000)" fill="currentColor"/></g>`,
"bubble-5": `<g transform="translate(1.000 2.632) scale(0.088235)"><path d="M0,0 L78,0 L92,3 L104,8 L115,15 L126,26 L133,37 L138,48 L141,60 L141,81 L137,97 L129,112 L117,125 L104,134 L92,139 L84,141 L26,142 L15,149 L2,159 L-11,167 L-18,167 L-23,164 L-26,161 L-27,158 L-28,131 L-39,124 L-48,115 L-55,104 L-60,93 L-63,78 L-63,65 L-60,50 L-54,36 L-46,25 L-37,16 L-23,7 L-10,2 Z M49,9 L-3,10 L-16,14 L-27,20 L-39,31 L-48,45 L-52,57 L-53,62 L-53,81 L-50,92 L-44,104 L-34,116 L-24,123 L-19,126 L-18,156 L-16,158 L-12,157 L5,145 L21,134 L23,133 L81,132 L92,129 L104,123 L115,114 L124,102 L129,91 L132,75 L131,59 L126,44 L119,32 L110,23 L99,16 L87,11 L74,9 Z " transform="translate(63.000 0.000)" fill="currentColor"/><path d="M0,0 L7,0 L11,4 L12,9 L9,14 L7,16 L-1,16 L-5,11 L-5,5 Z " transform="translate(48.000 67.000)" fill="currentColor"/><path d="M0,0 L7,0 L11,4 L11,12 L7,16 L0,16 L-5,11 L-5,5 Z " transform="translate(82.000 67.000)" fill="currentColor"/><path d="M0,0 L7,0 L12,5 L12,11 L7,16 L0,16 L-4,12 L-4,4 Z " transform="translate(116.000 67.000)" fill="currentColor"/><path d="M0,0 L7,0 L11,4 L11,12 L6,16 L0,16 L-4,12 L-5,6 L-2,1 Z " transform="translate(150.000 67.000)" fill="currentColor"/></g>`,
"bullet-list": `<g transform="translate(1.000 1.615) scale(0.111801)"><path d="M0,0 L7,0 L15,4 L20,12 L20,22 L17,28 L11,33 L8,34 L-1,34 L-8,30 L-12,25 L-14,19 L-13,11 L-9,5 L-3,1 Z M2,9 L-3,12 L-4,14 L-4,21 L1,25 L8,24 L11,20 L11,14 L8,10 Z " transform="translate(14.000 0.000)" fill="currentColor"/><path d="M0,0 L97,0 L100,2 L100,8 L98,10 L-1,10 L-4,7 L-3,2 Z " transform="translate(61.000 12.000)" fill="currentColor"/><path d="M0,0 L9,1 L15,5 L19,12 L19,22 L15,29 L10,33 L7,34 L-2,34 L-8,31 L-13,25 L-15,19 L-14,11 L-10,5 L-4,1 Z M0,9 L-5,13 L-5,21 L0,25 L7,24 L10,20 L10,14 L7,10 Z " transform="translate(15.000 58.000)" fill="currentColor"/><path d="M0,0 L98,0 L101,2 L100,9 L98,10 L0,10 L-3,6 L-1,1 Z " transform="translate(60.000 70.000)" fill="currentColor"/><path d="M0,0 L9,0 L15,3 L20,9 L21,11 L21,22 L16,30 L11,33 L2,34 L-5,31 L-10,26 L-12,23 L-13,15 L-10,7 L-3,1 Z M1,9 L-3,13 L-3,20 L1,24 L8,24 L12,20 L12,14 L8,9 Z " transform="translate(13.000 116.000)" fill="currentColor"/><path d="M0,0 L99,0 L101,2 L101,7 L98,10 L1,10 L-3,6 L-2,1 Z " transform="translate(60.000 128.000)" fill="currentColor"/></g>`,
"check": `<g transform="translate(1.000 4.126) scale(0.107784)"><path d="M0,0 L7,1 L9,4 L9,10 L-86,105 L-94,109 L-106,109 L-115,104 L-124,95 L-128,90 L-133,86 L-140,78 L-157,61 L-158,54 L-155,50 L-148,50 L-107,91 L-103,95 L-97,95 L-89,88 L-2,1 Z " transform="translate(158.000 0.000)" fill="currentColor"/></g>`,
Expand Down
Loading
Loading