Skip to content

feat: split desktop panel and thread locale into skills#2

Merged
Astro-Han merged 5 commits intodevfrom
feat/panel-split-skill-rewrite
Apr 15, 2026
Merged

feat: split desktop panel and thread locale into skills#2
Astro-Han merged 5 commits intodevfrom
feat/panel-split-skill-rewrite

Conversation

@Astro-Han
Copy link
Copy Markdown
Owner

Summary

  • split the desktop session side panel into separate Files and Review header toggles, defaulting desktop sessions to Files
  • thread user locale from the app through prompt submit, followups, commands, resumed loops, and built-in skill launches
  • rewrite the built-in document-processing, data-analysis, and writing-assistant skills to use mandatory question-first flows with locale-aware guidance
  • add regression tests for panel behavior, locale threading, skill content, skill launch locale, and SDK config model-id typing

Verification

  • packages/app: bun test --preload ./happydom.ts ./src/context/layout.test.ts ./src/components/prompt-input/submit.test.ts ./src/components/session/session-new-view.test.ts
  • packages/app: bun x playwright test e2e/commands/panels.spec.ts
  • packages/opencode: bun test ./test/session/system.test.ts ./test/session/prompt.test.ts ./test/skill/skill.test.ts --timeout 30000
  • packages/opencode: bun run typecheck
  • packages/sdk/js: bun run typecheck

Notes

  • packages/app bun run typecheck in this worktree still resolves @opencode-ai/sdk through the shared borrowed node_modules from the main checkout, so it reports stale pre-locale request types instead of the regenerated SDK types in this branch.

Remove JSON examples and `question` tool name references from all three
built-in skills. Gate blocks now use natural language flow control
("ask clarifying questions first, then act") instead of binding to a
specific tool, following the Superpowers skill pattern. Models pick the
right tool from the tool list at runtime.
@Astro-Han Astro-Han merged commit 4dce494 into dev Apr 15, 2026
@Astro-Han Astro-Han deleted the feat/panel-split-skill-rewrite branch April 15, 2026 12:42
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
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 May 4, 2026
…428)

Issue #428: SessionTurn used `findLast` over assistant messages, so any user prompt that triggered multiple assistant rounds (compaction, tool retries, multi-tool plans) silently lost earlier file changes from the Changes block. Users could no longer review or undo most of what the agent had written. This PR aggregates every assistant message that shares `parentID === userMessageID` into one turn-level Changes block with a single Undo / Reapply control.

## Backend (packages/opencode)

- `RESTORE_LIMIT` raised from 2 MB to 20 MB so multi-step edits in one user turn fit in the restore table; the 2 MB display window still gates the diff panel.
- `aggregateTurnInternal` collects all assistant messages under the same userMessageID, collapses RestoreRow entries by absolute path (preserving first-seen `before`, taking last-seen `after`), and runs a second-pass `disambiguateAggregatedRestoreFiles` so two sibling assistants editing different opaque external paths sharing a basename do not collide on the same `displayPath` (e.g. `/tmp/a/config.json` and `/tmp/b/config.json` become `config.json` and `config.json · external #2`).
- `preflightTurn` chains a virtual-disk state map across messages so chained same-file edits across two assistants no longer false-conflict; classifies per-message blocked items into three tiers matching single-message `mutate()` (`permission_denied` > `unsupported_size` > `conflict`); returns fatal `unsupported_size` immediately when any target state is non-restorable, so an aggregated undo/redo with one oversized/binary file blocks the whole turn instead of partially mutating sibling assistants under `force=true`.
- `mutateTurn` runs preflight first; with `force=false` returns `blocked` on any skip, with `force=true` skips the conflicting message, mid-loop rollbacks completed messages on a per-iteration write failure, and reports `mutatedPaths` (only paths actually written) plus `skipped[]`. Rollback failures surface as a dedicated `rollback_failed` reason rather than being collapsed into `write_failed`.
- New HTTP routes: `GET /session/:sessionID/turn/:userMessageID/changes`, `POST .../changes/undo`, `POST .../changes/redo` with `{ force: boolean }` body. `publishTurnChangeFiles` filters to `mutatedPaths` so File events only fire for paths that were really touched.
- White-busy filter: files created and then deleted within the same turn drop out of the aggregated display so they never reach the user as a phantom change.
- Mixed-state turn (one assistant applied, another undone) keeps both Undo and Reapply available; intentional single-button choice documented inline.

## Frontend (packages/app, packages/ui)

- `SessionTurn` no longer uses `findLast`. The Changes block renders the aggregated `display.files` with one Undo / Reapply button, an additions/deletions summary, and per-file status. Status badge for fully undone turns and a Reapply control are new.
- New helper module `turn-change-fetch.ts` plus `turn-change-fetch.test.ts` derive the fetch signature and target list from completed assistant siblings, so the timeline re-fetches when a new sibling assistant under the same parent completes.
- Conflict path: when preflight returns `blocked` with `reason: "conflict"`, a Kobalte Dialog confirms with the user, lists the skipped paths in a tinted bordered panel (`+N more` overflow label localized via `ui.sessionTurn.turnChanges.confirmListMore` in en + zh), and re-issues the request with `force=true`. A yellow skipped notice on the bottom of the Changes block surfaces partial-skip outcomes after a force apply.
- Trailing icons (open file / show in folder) now have `<Tooltip>` wrappers; Changes panel rows tightened (header 48 -> 36, action 28 -> 24, row 44 -> 32, chevron 24 -> 20) to fit denser turns without scrolling.
- Retry hardening: a 500 ms re-fetch on initial empty response, plus a `createEffect(on(sessionID, ...))` that cancels pending retry timers and clears `fetchedTurnChanges` when the active session changes, so a stale retry queued under the previous session cannot land on a freshly opened one.
- Marks the message dirty when rollback blocks with no files so the UI does not stay in a stuck loading state.
- English copy polished for `session.turnChange.blocked.*`: contractions, jargon removed, references the user's brand name.

## Tests

New regression coverage in `turn-change-aggregate.test.ts` and `turn-change-aggregate-routes.test.ts`:

- collapse two assistants editing different files into one display
- collapse same-file edits across two assistants into single net diff
- white-busy file (created then deleted) filtered out
- ignore assistant messages with a different parent
- undo / redo all assistant changes in a turn
- block whole turn on conflict by default; `force=true` reports `skipped[]`
- chained same-file edits undo cleanly without false conflict
- two assistants editing different absolute paths sharing a basename do not merge
- opaque external basenames from sibling assistants disambiguate after aggregation
- mixed-state turn keeps both undo and redo available
- `aggregateTurnUndo` reports `mutatedPaths` only for messages actually written
- preflight returns fatal `unsupported_size` when any assistant has an unrestorable target (both `force=false` and `force=true`)
- `MutationResultSchema` accepts `rollback_failed` as a blocked reason

Plus existing `turn-change.test.ts` extended for the new restore-limit boundaries (medium and huge file states).

## Verification

- typecheck across 9 packages (tsgo): green
- packages/opencode session + server tests: 594 passed / 4 skipped / 1 todo / 0 failed across 44 files
- Manual UI verification (Electron): two-assistant turn with three files (one shared), tampered-file conflict path, force-apply skipped notice
- Two rounds of crosscheck (Claude Opus + Codex), one round of CodeRabbit review (4 inline comments addressed)

## Risk Notes

- 20 MB per-file restore quota grows the SQLite turn-change tables for sessions with very large edits; existing per-message rows still apply, no migration.
- Aggregate route is additive; legacy single-message `/turn-change/:messageID/{undo,redo}` endpoints unchanged.
- Force-undo with conflicts is now a partial operation; the UI renders a skipped notice and File events only fire for `mutatedPaths`.

Closes #428.
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.
Astro-Han added a commit that referenced this pull request May 5, 2026
…#2, issue #440) (#448)

## Slice 02 of issue #440 — token housekeeping, typography wiring, icon CSS, colors-generation test

**Commits squashed (7):**
- refactor(ui): add icon color state selectors and document size tiers
- refactor(ui): replace inline font-size literals with font-size tokens
- refactor(ui): remove shadow/blend tokens from Tailwind --color-* namespace
- refactor(ui): promote --surface-base-active to REGULATED token section
- test(ui): add colors.txt ↔ colors.css generation consistency check
- test(ui): align colors-generation parser with generator script
- test(ui): add duplicate-token guard to colors-generation test

**Why:**
- Icon CSS now declares `[data-color]` state selectors for strong/weak/disabled; size tiers (small/normal/medium/large) documented in comment
- All inline `font-size` px literals in component CSS replaced with `var(--font-size-*)` tokens; establishes consistent token dependency chain
- Shadow, blend, and overlay tokens removed from `--color-*` Tailwind namespace — those utilities were never used and conflated design-token categories
- `--surface-base-active` moved from ad-hoc section to REGULATED block so downstream color-generation tests can pick it up
- New bidirectional consistency test (colors.txt ↔ tailwind/colors.css) catches stale generated artifacts; parser aligned with generator logic (`split(":")[0].trim().substring(2)`) and token-name charset relaxed to `[^\s:]+` to handle non-kebab names; duplicate-token guards added to prevent silent Set-dedup masking

**Key decisions:**
- Parser uses same `split(":")[0]` approach as `script/tailwind.ts` — not a regex — so any future token-name character additions are automatically handled
- `indexOf`-based dedup check preferred over frequency-map approach: simpler and directly catches the actual failure mode (duplicate in one file)
- Desktop-smoke runner flake (11+ min stuck run) diagnosed as GitHub macOS runner congestion, not a code regression; rerun passed in 2m54s
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant