chore: v1.1 polish — skills, panel fixes, README SEO#1
Merged
Conversation
- Description fields now use "Use when..." trigger format - Unified section structure: Clarify Before Acting / Tool Selection / Workflow / Guardrails - Removed redundant Error Handling + Output Requirements sections - ~30% fewer words while preserving all functional guidance
…mary The auto-open effect watched turnDiffs (from lastUserMessage.summary.diffs) which could be empty or stale during streaming. Switch to diffs() which reads from sync.data.session_diff (the Snapshot mechanism), the authoritative data source that the Files tab already uses.
The standalone diff icon was removed earlier, making the file-tree button the only toggle for the side panel. Update highlight condition from opened() && tab() === "files" to just opened() so the button stays active on the Changes tab too.
- Add concrete use cases (documents, data analysis, writing, PDF) - Add badges (license, platform) - Add "Who This Is For" section with search-friendly keywords - Fix license from MIT to Apache-2.0 - Screenshot placeholder for future asset - Task-focused language instead of generic "AI workstation"
Astro-Han
commented
Apr 15, 2026
Owner
Author
Astro-Han
left a comment
There was a problem hiding this comment.
Posting the requested review finding.
Snapshot (sync.data.session_diff) is only fetched when wantsReview is true, so reopened sessions with the panel closed would see empty diffs. Fall back to turnDiffs (message summary) when Snapshot data is not yet available, keeping SSE-pushed Snapshot as the primary source for active sessions.
The button's action (toggleTab("files")) and label ("Toggle file tree")
are Files-tab-specific, so the active/aria-expanded state should match.
Highlighting on the Changes tab would be misleading since the file tree
is not visible.
Astro-Han
added a commit
that referenced
this pull request
Apr 15, 2026
- Fix locale source: use oc_locale cookie, not Intl API (P1) - Add question tool schema details to skill questions (P1) - Add aria-expanded to diff button spec (P2) - Narrow-screen changes deferred to separate patch (Design #1)
This was referenced Apr 18, 2026
Astro-Han
added a commit
that referenced
this pull request
Apr 23, 2026
Remove centered Mark logo, add subtle local-processing reassurance note below composer, tune typography and spacing to match the #151 mockup. Also fix a latent bug where the title used text-24-medium (not defined in packages/ui utilities); now uses text-20-medium, the largest defined size utility. Pure visual change — Skill card onClick behavior and zh/en card copy unchanged. Refs #151 (PR #1 commit 1 of 2).
Astro-Han
added a commit
that referenced
this pull request
Apr 23, 2026
Replace the three generic developer-oriented icons with mockup-faithful custom SVGs (folder / bar-chart / pencil) tinted per Skill (warm orange / success green / violet) via semantic design tokens plus one arbitrary hex for violet since no violet token exists in the design system. Card visual switches from vertical card with description text to inline pill (icon + title side by side, no description), matching the mockup's light button style. Neutral chip border at rest, lifted border on hover. Click behavior unchanged. Home shell layout refines to match the #151 mockup: shift content down via pt-[28vh], compact title→subtitle→pills rhythm, restore subtitle with new copy "PawWork 可以帮你处理文件、分析信息、撰写内容并完成各类任务。" and its English counterpart. The upstream Icon component is kept in sidebar-items.tsx for the skill badge (falls back to folder / status / pencil-line), so badge icons differ from home for data-analysis during the PR #1 → PR #2 interval; PR #2 removes the badge entirely. Refs #151 (PR #1 commit 2 of 2).
Astro-Han
added a commit
that referenced
this pull request
Apr 23, 2026
Remove centered Mark logo, add subtle local-processing reassurance note below composer, tune typography and spacing to match the #151 mockup. Also fix a latent bug where the title used text-24-medium (not defined in packages/ui utilities); now uses text-20-medium, the largest defined size utility. Pure visual change — Skill card onClick behavior and zh/en card copy unchanged. Refs #151 (PR #1 commit 1 of 2).
Astro-Han
added a commit
that referenced
this pull request
Apr 23, 2026
Replace the three generic developer-oriented icons with mockup-faithful custom SVGs (folder / bar-chart / pencil) tinted per Skill (warm orange / success green / violet) via semantic design tokens plus one arbitrary hex for violet since no violet token exists in the design system. Card visual switches from vertical card with description text to inline pill (icon + title side by side, no description), matching the mockup's light button style. Neutral chip border at rest, lifted border on hover. Click behavior unchanged. Home shell layout refines to match the #151 mockup: shift content down via pt-[28vh], compact title→subtitle→pills rhythm, restore subtitle with new copy "PawWork 可以帮你处理文件、分析信息、撰写内容并完成各类任务。" and its English counterpart. The upstream Icon component is kept in sidebar-items.tsx for the skill badge (falls back to folder / status / pencil-line), so badge icons differ from home for data-analysis during the PR #1 → PR #2 interval; PR #2 removes the badge entirely. Refs #151 (PR #1 commit 2 of 2).
This was referenced Apr 25, 2026
This was referenced May 5, 2026
Astro-Han
added a commit
that referenced
this pull request
May 5, 2026
…rity test (slice #1, #440) - add dark overrides for success/warning/error (bg/text/base), diff-add/del, icon-disabled, ring-base — light pastels were inheriting into dark mode - restore 4 tokens dropped during theme.css rewrite: font-family-mono--font-feature-settings, shadow-xs-border-base, shadow-lg-border-base, surface-base-active (with dark counterparts) - extend parity test: dark-completeness assertion (light regulated tokens must have dark override or be in SAME_IN_DARK), @media mirror ↔ [data-color-scheme="dark"] exact-match suite (115 pass, was 64) - remove one-time migration script slice-01-rename.ts
Astro-Han
added a commit
that referenced
this pull request
May 5, 2026
…lass sweep (slice #1, #440) - dark shadow-raised/floating/modal: remove drop shadows, keep inset highlight only (L18 bans drop shadows in dark mode) - surface-sunken light: alias to --bg-cream (#faf9f7), not a third neutral tier (L9 specifies 2 tiers only) - replace 22 invalid Tailwind token classes across 13 component files: bg-background-strong → bg-surface-raised, text-text-subtle → text-fg-weak, text-text-danger-base → text-error, ring-interactive-base → ring-brand-primary, icon-strong-base → text-icon-strong, border-border-subtle → border-border-weak, border-t-accent-base → border-t-brand-primary, hover:border-border-strong → hover:border-border-base - fix theme.css comment: dataset.colorScheme is written in context.tsx, not loader.ts
Astro-Han
added a commit
that referenced
this pull request
May 5, 2026
…ssue #440) Slice #1 of eleven. Establishes the semantic token foundation for all subsequent PawWork UI component slices (#2–#11). This is a permanent carve-out from upstream opencode — see AGENTS.md §Upstream Sync and .gitattributes. ## What changed - theme.css: full rewrite against PawWork STANDARDS (warm neutrals, brand orange #ff5910, 13px dense, system-font). Three-selector structure: :root (light) → [data-color-scheme="dark"] (authoritative dark) → @media mirror Fixes a latent upstream bug: @media-only meant the Settings toggle had no effect. - pawwork.json: rewritten to STANDARDS values with new token names. - colors.txt → tailwind/colors.css: 104 tokens (incl. legacy compat aliases); regenerated Tailwind --color-* bridge via script/tailwind.ts. - 50+ token renames across 144 component files in packages/ui/src and packages/app/src. - HTML entry files (app/index.html, desktop-electron renderer/*.html): updated to var(--bg-base) and synced theme-color meta. Now in .gitattributes carve-out. - UNREGULATED section in theme.css: holds legacy compat aliases (button-brand-*, icon-success-base, icon-info-base, icon-on-interactive-base, surface-base-active) so existing component consumers stay styled without entering STANDARDS managed set. - .gitattributes: added merge=pawwork-keep-ours driver entries for packages/ui/** and key packages/app paths. Driver must be registered per-clone: git config merge.pawwork-keep-ours.driver "true" Verify before upstream sync: bash packages/ui/script/verify-merge-driver.sh ## Tests added - packages/ui/test/theme-parity.test.ts (128 assertions, 0 fail): light root ↔ pawwork.json light.overrides, dark block ↔ dark.overrides, dark completeness (SAME_IN_DARK set), @media mirror exact-match, and two runtime-critical non-regulated token assertions (--text-mix-blend-mode: plus-lighter). - packages/ui/test/undefined-tokens.test.ts: Scans every var(--xxx) in theme.css, Tailwind bridge, and HTML entry files; asserts each resolves to a definition. ## Deferred to slice #2 - colors.txt ↔ colors.css generation consistency test (manual regen + review for now) - TSX/class utility audit (old token utility names, misspelled utilities) - --surface-base-active classification (state token vs. compat bridge) - Legacy runtime token deprecation checklist ## Key decisions for future agents 1. [data-color-scheme="dark"] is authoritative. @media block is mirror-only (first-paint). Both must stay in sync — theme-parity.test.ts enforces this. 2. UNREGULATED section in theme.css is intentional scope. Tokens there are consumed by components but not STANDARDS-managed. Do not promote to regulated without adding to pawwork.json and updating the parity test. 3. The merge driver only fires on two-sided conflicts. One-sided upstream changes still clean-merge. Always review upstream-sync PR diffs for carve-out paths. 4. colors.css is a generated file (do not hand-edit). Regen: bun run script/tailwind.ts.
Astro-Han
added a commit
that referenced
this pull request
May 5, 2026
…ssue #440) Slice #1 of eleven. Establishes the semantic token foundation for all subsequent PawWork UI component slices (#2–#11). This is a permanent carve-out from upstream opencode — see AGENTS.md §Upstream Sync and .gitattributes. ## What changed - theme.css: full rewrite against PawWork STANDARDS (warm neutrals, brand orange #ff5910, 13px dense, system-font). Three-selector structure: :root (light) → [data-color-scheme="dark"] (authoritative dark) → @media mirror Fixes a latent upstream bug: @media-only meant the Settings toggle had no effect. - pawwork.json: rewritten to STANDARDS values with new token names. - colors.txt → tailwind/colors.css: 104 tokens (incl. legacy compat aliases); regenerated Tailwind --color-* bridge via script/tailwind.ts. - 50+ token renames across 144 component files in packages/ui/src and packages/app/src. - HTML entry files (app/index.html, desktop-electron renderer/*.html): updated to var(--bg-base) and synced theme-color meta. Now in .gitattributes carve-out. - UNREGULATED section in theme.css: holds legacy compat aliases (button-brand-*, icon-success-base, icon-info-base, icon-on-interactive-base, surface-base-active) so existing component consumers stay styled without entering STANDARDS managed set. - .gitattributes: added merge=pawwork-keep-ours driver entries for packages/ui/** and key packages/app paths. Driver must be registered per-clone: git config merge.pawwork-keep-ours.driver "true" Verify before upstream sync: bash packages/ui/script/verify-merge-driver.sh ## Tests added - packages/ui/test/theme-parity.test.ts (128 assertions, 0 fail): light root ↔ pawwork.json light.overrides, dark block ↔ dark.overrides, dark completeness (SAME_IN_DARK set), @media mirror exact-match, and two runtime-critical non-regulated token assertions (--text-mix-blend-mode: plus-lighter). - packages/ui/test/undefined-tokens.test.ts: Scans every var(--xxx) in theme.css, Tailwind bridge, and HTML entry files; asserts each resolves to a definition. ## Deferred to slice #2 - colors.txt ↔ colors.css generation consistency test (manual regen + review for now) - TSX/class utility audit (old token utility names, misspelled utilities) - --surface-base-active classification (state token vs. compat bridge) - Legacy runtime token deprecation checklist ## Key decisions for future agents 1. [data-color-scheme="dark"] is authoritative. @media block is mirror-only (first-paint). Both must stay in sync — theme-parity.test.ts enforces this. 2. UNREGULATED section in theme.css is intentional scope. Tokens there are consumed by components but not STANDARDS-managed. Do not promote to regulated without adding to pawwork.json and updating the parity test. 3. The merge driver only fires on two-sided conflicts. One-sided upstream changes still clean-merge. Always review upstream-sync PR diffs for carve-out paths. 4. colors.css is a generated file (do not hand-edit). Regen: bun run script/tailwind.ts.
11 tasks
Astro-Han
added a commit
that referenced
this pull request
May 7, 2026
Trace cell #1 of imagegen Sheet 12 (decreasing list bars + leading down arrow, sort-desc semantics). Rescaled to landscape keyshape 18×14 inside the 20×20 viewBox (1-unit margin preserved). Replaces the sliders placeholder on the sidebar sort trigger.
Astro-Han
added a commit
that referenced
this pull request
May 8, 2026
* feat(app): add sidebar sort label and short option keys * feat(app): extract sidebar 4-state status kind helper * feat(app): rewrite sidebar session row to L35 (h32, 4-state status) Drop the dead ProjectIcon export (no callers) and rewrite the row outer shell + right-slot status system to the L35 lock: - Row: fixed h-8, padding 0 10, radius-sm, flex-centered (no leading-[1.4]) - Active title: text-fg-strong + font-medium - Status: 4 states asking | busy | error | time, exclusive, opacity-faded on hover/focus/menu-open (never display:none) - Drop the unseen / pinned-icon visual states; pin signal is now carried by the Pinned section position, unread signal is dropped (L35 lock has no slot for it) - Drop SessionItemProps.pinned and SessionItemProps.dense; menu state reads pinnedIDs() directly in pawwork-sidebar - Sub-session indentation kept at level*16 as a deliberate departure * feat(app): add shortcut hint field to SessionMenuAction * feat(app): rebuild sidebar shell with sort popover and four-segment layout - Replace icon-only sort toggle with text+chev DropdownMenu (排序 / Sort) exposing two options 按时间 / 按项目 (By time / By project) per L35. - Add data-component "pawwork-side-traffic" 32-high placeholder per L37 so slice 17 can drop in macOS traffic-lights and the collapse button without reflowing geometry. - Add data-component "pawwork-side-top|side-scroll|side-foot" markers on existing segments so the L37 four-segment shape is testable. - Inline ⌘, hint in footer (drops TooltipKeybind wrapper); kbd container styling lands in slice 14. - Render Rename ↵ / Delete ⌫ shortcut hints in dropdown + context menus. * style(app): scope sidebar row iconbtn to L35 hover (radius-md + 0.06 deeper) Slice 08 collapsed Icon size=small to 16px globally, so the leading-slot 14px override is no longer load-bearing — drop it and let NewSessionItem render at the standard chrome size. Add a row-scoped rule that bumps the action-overlay iconbtn radius to md and its hover to --row-active-overlay (0.06 alpha, one step deeper than the row's 0.04) so it reads as its own target on a hovered row. * test(app): e2e for slice 09 sidebar shell + align sort selector Add sidebar-slice-09.spec.ts covering: - 4-state right-slot status + 4-item menu with Rename ↵ / Delete ⌫ hints - Sort trigger as text+chev popover with two options (按时间/按项目) - L37 four-segment shape (side-traffic 32 above side-top, side-foot) Patch sidebar-session-organization.spec.ts to use the new pawwork-sort-trigger selector and pick the project option from the popover instead of toggling the old ghost icon button. * fix(app): use literal px for slice 09 row + status geometry PawWork sets root font size to 13px (dense desktop), so Tailwind h-N sizing scales rem-based and h-8 ends up 26px instead of L35's 32. Pin the row outer (h-32), status slot (20×20), Spinner (16×16), and side-traffic placeholder (h-32) to literal pixels. Drop the now-stale pin-button-in-leading-slot assertion in sidebar-leading-slot.spec.ts: per L35 the row no longer renders a pin glyph, so pin status is signaled by the row's presence in the Pinned section (still asserted). * fix(app): drop dead Icon size prop after slice 08 collapse Slice 08 (#440, 6889e01) collapsed the Icon multi-size API to a fixed 16x16 contract; `size` is no longer in IconProps. Strip the leftover `size="small"` from 7 sidebar callsites that survived our pre-merge state. Functionally a no-op since the rendered size was already 16x16, but the prop is now a TS error. * fix(app): drop side-traffic placeholder until slice 17 owns the chrome The L37 spec reserved a 32px top segment so slice 17 (which moves the macOS traffic-lights into the sidebar) wouldn't reflow geometry. While the OS still paints traffic-lights on its window chrome, the placeholder just doubles the empty space at the top. Drop it; slice 17 will reintroduce the segment when it actually fills it. Updates the four-segment e2e to the three-segment shape it reflects. * fix(app): unify sidebar quick-action buttons under TooltipKeybind The slice 09 footer button rendered an inline `⌘,` next to its label, while the new-session and search buttons above it had no shortcut hint at all. Three buttons in the same column with three different shortcut treatments looks unintentional. Wrap all three with TooltipKeybind so the keybind reveals on hover with the same affordance everywhere; thread `newSessionKeybind` and `searchKeybind` props from layout.tsx, mirroring `settingsKeybind`. Pure visual consolidation; behavior unchanged. * fix(app): keep sidebar status time text on a single line The right-slot status box was forced to a 20×20 square (`size-[20px]`), which fits icons (16px) but truncated time text like "刚刚" into a vertical character stack on narrow sidebars. Switch the inner box to `h-full min-w-[20px]` so it stays icon-sized when the slot holds an Icon/Spinner but flexes horizontally for text content. Add `whitespace-nowrap` on the time `<span>` as belt + suspenders. * fix(app): unify sidebar action buttons to 32px nav-row height The new-session, search, and settings buttons rendered at ~30px (`py-1.5` + content), while the L35 session row was 32px. The 2-4px gap looked unintentional in a single sidebar column. Treat the whole sidebar as one nav system: lock all three top/foot buttons + the session row to 32px, matching the L30/L31 list-row family. This is a deliberate sidebar-scoped deviation from L24 buttons 28 (which still applies to dialog/composer/form Buttons elsewhere — those never sit next to list rows so the visual conflict doesn't arise). Same shape Codex.app uses with its single `--height-token-nav-row` token. * style(app): apply --duration-fast to sidebar row transition per L35 L35 locks row hover/active transition at `var(--duration-fast)` (80ms) ease-out. The Tailwind `transition-colors` shorthand defaulted to 150ms cubic-bezier — close, not exact. Add a CSS rule scoped to the row so hover/active state changes feel as snappy as the spec asks. * fix(app): use 13px caption + h24 sort trigger per L35 typography lock L35 specifies section labels ("已置顶", "全部会话", project group headers) and the sort trigger as `--type-caption` (= 400 13px/130%, 500 weight on the trigger). The implementation used `text-12-regular` / `text-12-medium` (12px), shaving 1px off the font and breaking visual rhythm against the 13px row title. Lock all four section headers to `text-13-regular` and the sort trigger to `text-13-medium`, plus pin the trigger height to a literal `h-[24px]` to avoid drift if base font-size ever shifts. * fix(app): lock sidebar section header containers to 32px nav-row The section header bands ("已置顶", "全部会话" + sort trigger, project group titles in project-mode) used `mt-3 + pt-3 + pb-2` ad-hoc spacing that summed to ~44px — out of step with the 32px nav-row rhythm we just unified across the rest of the sidebar. Lock all three to `h-[32px] flex items-center px-2` so every visual unit in the sidebar lives on the same 32-grid. Section headers still read as non-clickable separators (no hover background, fg-weak text). The sort trigger remains h24 nested inside, centered with breathing room above and below. * fix(app): apply affinity spacing to sidebar section headers Locking section headers to 32 left them visually equidistant from the section above and the rows inside, which made the eye read them as "another row" rather than a separator. Apply the affinity-spacing rule (Refactoring UI): - mt-4 (16px) above each section header — clear gap from the previous group, signaling "new section" - 0 below — header sits tight against its own rows so the eye groups them as one unit (Gestalt proximity: header belongs to what's below) - first:mt-0 (or index() === 0 inside <For>) so the first visible section doesn't get a stray top gap Drops nav's `gap-1` since headers now own their breathing room explicitly. Inner section gap-0.5 (2px between sibling rows) preserved. * fix(app): always apply mt-4 to top-level sidebar section headers Dropping `first:mt-0` revealed why first-child guarding was wrong here: the user's view had no pinned section, so "全部会话" became the first nav child, took mt-0, and stuck to the search button above with no section break. The side-top button cluster and the first scroll section ARE different groups — affinity spacing demands the same 16px gap there as between any two sidebar sections. Always apply mt-4 to top-level section header containers (pinned, "全部会话"). Project group sections inside the For keep `index() === 0` guarding because the first group should attach to its containing "全部会话" header (header-to-content tight). * fix(app): drop sidebar footer divider in favor of affinity spacing The 1px `border-t` between scroll content and the settings footer was doing the same job as a section break — but with a hard line instead of breathing room. Now that affinity spacing carries every other section boundary in the sidebar, the divider is redundant noise. Replace `border-t border-border-weaker px-3 py-2` with `px-3 pt-4 pb-3` (16 above, 12 below — same 16px rhythm as section headers, 12 mirrors side-top's pt-3 for top/bottom symmetry to the sidebar edges). * feat(ui): lift primitive default heights to 32 Lift Button, TextField (normal variant), Picker trigger, and line-comment action default heights from 28 to 32 so primitive defaults align with the L35 sidebar rhythm (rows / pinned / settings all at 32) and Picker dropdown items (already at 32). This removes the trigger-to-item height jump and unifies the read / select / act surfaces. No size API added. The remaining 28 hardcoded usages are all on surfaces scheduled for removal or rework: dialog-select-model and dialog-manage-models move to settings Models pane per design preview, right-side panel tabs and session-new-view skill chips are pending separate redesigns. * fix(app): include question and blocker in sidebar asking signal The L35 right-slot asking state previously only tracked PermissionRequest, which left an agent ask() pause showing as busy in the sidebar while the main region correctly surfaced a question. Mirror the use-session-blockers OR set (permission || question-blocker || question) so the sidebar matches the main region semantics. Renames hasPermissions to isAsking to make the intent legible at the call site. * style(ui): replace pulse-block spinner with rotating ring The legacy 16-square pulse animation was an opencode-era brand mark and conflicts with the PawWork loading affordance shown in design preview (docs/design/ui_kits/desktop/styles.css L1407). Replace with a rotating ring built on a single div + border-right transparent + 700ms linear rotate. Keeps the component contract (class / classList / style passthrough, currentColor, 18px default), so all four call sites (sidebar row busy state, sidebar workspace, dialog-connect-provider, message-timeline) pick up the new look without changes. * style(ui): align spinner with STANDARDS keyshape and pw-spin keyframe Three drifts from the just-replaced ring against STANDARDS.md L60/L65/L128: - Keyframe was spinner-rotate; the L65 lock names pw-spin as the canonical 700ms rotation reused by spinner and the thinking ring. - Border was 1.4px (carried from the design preview todo spinner at 7px); L60 calls for 1.5px equivalent at 16. - Sidebar busy slot rendered the spinner at 16; the keyshape circle is Ø18 inside the 20x20 status box per L60/L128. * style(ui): slow spinner from 700ms to 1200ms Bring rotation rate down to a calmer cadence per design feedback. STANDARDS.md L60 / L65 updated locally so the doc and code agree. * style(app): lock sidebar status opacity to --duration-base 120ms Status default and overlay opacity were inheriting Tailwind's default 150ms transition-opacity, which sits outside the L22 three-tier ladder (80 / 120 / 240). Promote both to data-status-default / data-status-overlay attributes so sidebar.css can pin them to --duration-base (120ms) — the fastest tier where opacity fade still reads as smooth, leaving --duration-fast (80ms) for color-only swaps where perceptual budget is lower. * style(ui): lock dropdown menu items to 32px row family Dropdown items were rendering at ~26px (padding 4 + line-height-large), out of step with the L30 menu / L35 sidebar / picker item 32 row family. Pin min-height to 32 with horizontal padding 0 8, and swap the highlighted background from --surface-raised (solid panel tone) to --row-hover-overlay so menu / picker / list-row hovers all share the same overlay token. * refactor(app): swap session menu shortcut hints for leading icons The Rename ↵ / Delete ⌫ hint string was a placeholder — no command.keybind ever wired session.rename or session.delete, so the suffix advertised shortcuts that did nothing. Drop the shortcut field entirely (do not lie to users) and add a leading icon per row instead (pin / pencil-line / download / trash). Exports IconName from @opencode-ai/ui/icon so the action type can constrain icon to the registry instead of a free-form string. Updates the unit test to assert the icon registry order, and the e2e spec to assert leading icon-svg slots on each menuitem. * refactor(app): give sidebar sort options leading icons + check mark Sort menu was the only place left with naked text labels; align it with the session menu icon convention. Use schedule for By time and folder for By project as leading slots, and swap the active-state ✓ glyph for the check icon so the row carries icons in both leading and trailing slots. * refactor(app): switch sidebar sort trigger to icon-only IconButton Replace the text + chevron-down trigger with a 24x24 ghost IconButton wrapped in a Tooltip; brings the sort affordance under the L23 secondary-control standard so it sits inside the 32 section header with 4px breathing room above and below, matching the row menu IconButton elsewhere in the sidebar. Uses sliders as a placeholder icon (chevron-grabber-vertical reads as drag-handle, sliders is the closest fit in the current registry). A dedicated sort icon is queued in pawwork-chrome-icon-imagegen-v4-slice08 Sheet 12; swap once that lands. * feat(ui): add sort icon and wire it into sidebar trigger Trace cell #1 of imagegen Sheet 12 (decreasing list bars + leading down arrow, sort-desc semantics). Rescaled to landscape keyshape 18×14 inside the 20×20 viewBox (1-unit margin preserved). Replaces the sliders placeholder on the sidebar sort trigger. * fix(ui): align sort trigger hover and icon size with row-menu Two visual mismatches between the sidebar sort trigger and the session-row dot-grid trigger: - Hover affordance: row-menu uses radius-md + --row-active-overlay while the sort trigger fell back to the IconButton default radius-sm + --hover-overlay, reading visibly faded next to it. Share the row-menu rules. - Sort glyph: previous transform was height-bound at 14 inside the 20×20 viewBox, leaving the width at 17.43 and the icon looking smaller than dot-grid (which spans ~16). Re-scale width-bound to 18 so both axes use the live area; height becomes 14.46, still inside the 18×18 live area. * revert(ui): restore sort trigger and icon to L21/L23 spec Previous commit pulled the sort trigger onto the row-menu hover treatment (radius-md + 6% black) and stretched the sort glyph width-bound past the landscape 14-unit height ceiling. Both moves violate the standards: - L23 says IconButton ghost is one variant: radius-sm + 4% black. L35 explicitly carves out the row action-overlay (radius-md + 6%) with a written reason — the row already has a 0.04 hover layer to sit on top of. The sort trigger lives outside the row, so the L23 default is the correct fit. - L21 keyshapes are bounding boxes, not minima. Landscape is 18×14. The traced glyph aspect (1.245) is slightly squarer than 18×14 (1.286), so the height-bound scale (17.43×14) is what fits inside the keyshape; width-bound (18×14.46) overflows it. The cosmetic delta versus dot-grid is by design: row-menu and sort/footer iconbuttons are two different control classes. * feat(ui): unify IconButton hover to radius-md + 6% globally Two control classes (default 4% / radius-sm vs sidebar action-overlay 6% / radius-md) collapse into one. Visual differences inside the same sidebar (sort trigger vs row-menu) violated the elegance rule, and the written L35 carve-out shifted the judgment call onto every future contributor adding an IconButton. Changes: - icon-button.css: hover token --hover-overlay -> --row-active-overlay (4% -> 6%), radius --radius-sm -> --radius-md - sidebar.css: drop the row-menu override block; the new default matches it - pawwork-sidebar.tsx: drop the now-redundant class="rounded-md" on the row-menu trigger - icon-button-states.test.ts: track the token rename Token reuse: --row-active-overlay (named for list rows) is shared by IconButton hover at the same 6% tier — avoids minting a synonym token like --control-hover-overlay. Button (secondary/danger) hover stays on --hover-overlay (4%) because dialog/composer/form contexts read fine at 4% and a heavier overlay would compete with the surrounding content. STANDARDS.md L23 + L35 updated locally (untracked spec doc) to reflect the unified rule. * fix(app): align all sidebar inner padding to 10px to match row spec Session row pads 10px left/right per L35, but every adjacent surface fell back to a slightly tighter rhythm: - pinned label / sort header / project group header: px-2 (8px) - show-more / search-history rows: px-2 (8px) - new-session / search / settings buttons: pl-2 pr-3 (8/12) That left a 2px stair-step between section headers and the rows they introduce, and made the sort trigger sit 2px inboard of the row-menu trigger directly below it. Snap every container to px-2.5 (10px) so the entire sidebar shares one inner edge. * feat(app): add pin as 4th sidebar status priority (above time) Pinned idle sessions now show a pin glyph in the right slot instead of falling through to the timestamp. Priority becomes asking → busy → error → pin → time, matching the L35 mental model: the three live signals still preempt pin, but pin is meaningful enough to displace the passive time. - sidebar-status-kind.ts: add "pin" kind + pinned input; tests cover every pairwise priority and the pinned-only case. - sidebar-items.tsx: SessionItem accepts isPinned, statusKind feeds pinned only when no live signal is active, statusContent renders Icon name="pin" with text-icon-weak (passive-tier color, lighter than the live brand/error tints). - pawwork-sidebar.tsx: pass isPinned per row by checking pinnedIDs().includes(session.id). * revert(app): drop pin icon from sidebar right-slot, restore 4-state The "Pinned" section header at the top of the sidebar already signals which sessions are pinned — adding a per-row pin icon repeats the same information. The header is present in both sort modes (time and project), so location alone is sufficient. Restores the original 4-state right-slot priority: asking → busy → error → time. * refactor(ui): merge dropdown-menu and context-menu css into shared menu.css DropdownMenu (three-dots) and ContextMenu (right-click) were styled by two separate CSS files that had drifted: different row height, hover overlay, item radius, font size, content radius, and shadow. The two menus showed the same actions but didn't look the same. Collapse both stylesheets into a single menu.css using combined selectors that cover both Kobalte primitives. Trigger semantics still differ at the component layer; only the visual surface is unified. Future tweaks land once and apply everywhere — no sync drift. * fix(app): close settings overlay when navigating to a session from sidebar Clicking a session row or "New Session" while the settings overlay was open did nothing visible. The path-watcher effect intended to close settings on URL change captured pathname at unreliable moments and did not fire consistently, so the overlay stayed on top of the navigation target. Make the close explicit: shell-navigation now closes the settings surface as the first step of openSession and openNewSession (including the project-chooser fallback). Drop the path-watcher effect — direct caller-side close is the canonical path. * fix(app): shorten settings footer en label to "PawWork" "PawWork Desktop" was wider than the settings sidebar footer slot and read as redundant — every settings page is the desktop one. Drop the suffix in EN; ZH stays "爪印". * refactor(ui,app): polish command palette per L32 with fixed-height body The palette was tight: search row was a thin 40px placeholder with no icon, items used picker.css default 0/8 padding (off-spec, L32 wants 0/12), and the modal grew/shrank with result count which made every keystroke jump the page. Changes - Search header is now a 52px ghost strip with leading magnifying glass and 16px horizontal padding; input renders type-body without the 20px clamp that made the box read as cramped. - Body locks to 480px (or 100dvh - 32px on small windows) so result count changes scroll inside the modal instead of resizing it. - Items align to L32: height 32, padding 0/12, radius-sm; selected row uses surface-interactive-base + brand-primary check icon. - Section headers get type-h3 family (uppercase, 0.5 tracking) per L32; empty state grows to 48/32 padding. - Drop hideIcon from the file/command picker so the palette shows the search icon to match other List consumers' search affordance. * fix(app): unify composer picker trigger font weight to regular The model picker, variant Select, and workspace chip showed visibly different font weights in the composer footer. Two were rendered via the Button component (which sets font-weight: medium by default), and the existing override class "text-13-regular!" did not apply because Tailwind v4 only processes the "!" important suffix on its own utilities — not on custom CSS classes — so the bang was a silent no-op. Make all three triggers explicitly font-normal (Tailwind built-in), and clean up the bogus "!" suffixes (text-13-regular!, justify-start!) so the class lists describe what actually applies. * feat(app): collapsible project groups in sidebar When the sidebar is sorted by project, each project group can now be folded so the user can hide branches they're not actively working on without losing them. - Project header is a clickable button (whole row hot, not just the chev) with a leading folder icon to distinguish project groups from session rows, and a trailing chev that appears on hover and rotates -90° when collapsed. - Default expanded; folded state persists per-project label in the page store with a sanitizing migrate so corrupt entries can't bleed into runtime. - Toggle uses reconcile so removing a key actually drops it (default setStore on objects merges and would never clear the field). - Animate height via grid-template-rows 0fr↔1fr, 200ms exponential ease-out, motion-reduce friendly. Items stay mounted so scroll and focus survive the toggle; inert removes them from the tab order while collapsed. - Tighten inter-section margin from mt-4 to mt-0.5 so collapsed groups list at the same rhythm as session rows. The 16px section break in L35 is for top-level segments (Pinned / All / Footer), not project sub-groups. * test(app): cover project collapse and settings-close-on-nav with e2e The two new sidebar behaviors in slice-09 had no real-user-path coverage — only the shell-navigation unit test verified the close-on- navigate call order, and project collapse had no test at all. Per AGENTS.md, sidebar / dialog / menu interactions need E2E. - project-collapse.spec.ts: clicks the project group toggle, asserts aria-expanded / data-collapsed flips and the wrapper's bounding box height collapses to 0 (grid-template-rows 0fr clip). Second case reloads and asserts the persisted collapsed state survives. - settings-close-on-nav.spec.ts: opens the settings overlay, clicks a sidebar session, asserts the overlay hides and the URL navigates to the clicked session. Tag the collapse content wrapper with data-component / data-collapsed so the test can target the visible-hide signal — Playwright's toBeHidden treats overflow:hidden + 0fr clipping as visible since the clipped element keeps its layout height. * test(app): rename slice-09 sort spec and tighten shape assertions Sort trigger stayed icon-only after manual UX review (a text+chev trigger does not fit the L35 row beside the All header); rename the test to describe the actual behavior. Tighten the three-segment shape assertions so failures point to a specific bounding box rather than a boolean roll-up. * fix(ui): scope IconButton row-overlay tokens to sidebar row trigger The slice 09 change to --radius-md and --row-active-overlay was meant for the sidebar three-dot trigger that sits on top of the row's own hover layer; applying it to the global IconButton selector pulled the heavier overlay and wider radius into titlebar, prompt toolbar, and every other ghost icon button. Revert the base to --radius-sm and --hover-overlay (the documented IconButton contract), and add a sidebar-row-scoped override so the slice 09 visual still ships where intended. Tighten the contract test to assert the base radius and to match the hover token inside the &:hover rule rather than anywhere in the file. * fix(ui,app): localize Spinner aria-label and honour reduced motion The shared Spinner sat in the UI package with a hardcoded aria-label="Loading", so screen readers always announced English even when the rest of the sidebar was localized. Add an optional aria-label prop (default unchanged) and pass the translated string from the sidebar busy state. Also gate the infinite rotation behind prefers-reduced-motion so users with reduced-motion settings don't see a continuously spinning indicator. * fix(app): close settings overlay for non-shell navigation Slice 09 replaced the unreliable path-watcher effect with an explicit closeSettingsSurface call inside createShellNavigation, which covers sidebar-driven session opens. Notification clicks (and any other caller of @/utils/notification-click) route through useNavigate directly and were leaving the settings overlay on top of the new route. Wrap the navigate function injected into setNavigate so it closes the overlay before routing, restoring the same guarantee for non-shell entry points without bringing back the path-watcher effect. * test(app): assert icon order in 3-state session menu case The full 4-state case already locks down the icon sequence; mirror it in the exportAvailable: false path so a regression that swaps pin / rename / delete icons cannot slip through.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Test plan