Note: Placeholder / candidate. Not scheduled, not a commitment. Capturing the pattern while it's fresh from #208. This is a developer-facing foundation concern, not a user-facing workflow — the closest template fit is below.
What task are you trying to do?
Ship UI work on the composer, chips, and popovers without paying a CSS-override tax every PR.
PawWork's design language has permanently diverged from opencode's (pill shapes, brand orange #FF5910 / #E56A2E, rounded-full / radius-xl popovers, menu-below composer, custom WorkspaceChip / SendButton), but the underlying primitives still come from @opencode-ai/ui (Kobalte wrapper + popover.css + shared Button variants). Every feature PR that touches chips or popovers pays a tax to override upstream styling, and most of the iteration rounds on #208 were spent fighting that tax rather than moving the product forward.
What do you do today?
Patch case by case. From #208 alone:
- Three popovers (
dialog-select-model, prompt-input variant, workspace-chip) use inline style={{ "border-radius": "16px" }} to beat [data-component="popover-content"] { border-radius: var(--radius-md) }. Tailwind ! modifier and class both lose to source order.
SendButton uses custom #E56A2E + inline color: #ffffff on the Icon to beat [data-component="icon"] { color: var(--icon-base) }.
as any cast on Kobalte triggerProps to inject data-action attrs (Popover types don't accept data-*).
- Model chip gets hover via Button component; Variant and Workspace chips rebuild hover manually. One round used invalid
hover:bg-background-base-hover token (silently dropped by Tailwind) — only visual review caught it.
- Starter cards iterated
rounded-xl → rounded-2xl → rounded-full across three rounds because no shared chip primitive pinned the shape.
None of these are bugs. They're all the same shape: brand-layer design colliding with a primitive layer that assumes a different design.
What would a good result look like?
Three focused replacements, each a self-contained sub-PR:
- PawWork Popover primitive — thin wrapper over Kobalte.Popover that does NOT pull
popover.css. Radius / background / border baked into Tailwind at the primitive layer. Migrate model / variant / workspace popovers off the shared @opencode-ai/ui/popover wrapper.
<Chip> primitive — unified trigger for Model / Variant / Workspace chips. Hover, focus, disabled, truncation in one place. Removes piggyback on Button and removes the "why does this chip not hover like that chip" asymmetry.
- Brand-layer design tokens — explicit
--pawwork-accent-brand, --pawwork-radius-chip, --pawwork-radius-popover, --pawwork-shadow-chip. Stop co-opting @opencode-ai/ui tokens (--radius-md, --accent-brand) whose semantics live elsewhere.
Acceptance criteria:
- Zero inline
style={{ "border-radius": ... }} in prompt-input.tsx, workspace-chip.tsx, dialog-select-model.tsx
- Zero
as any casts on Popover trigger props in packages/app/src/components
- A new feature PR adding a chip-shaped trigger requires zero CSS override to match design
- Brand orange / chip radius / popover radius have a single source of truth in the PawWork layer
Audience
Both — this is developer-facing, but it directly improves the speed and quality of every UI iteration that reaches users. Visible payoff is faster, more consistent visual updates for everyone.
Extra context
Non-goals. Not rewriting sidebar / session list / command palette / tool-execution UI / permission dialogs / chat stream rendering. Not replacing @opencode-ai/ui/button wholesale. Not a frontend rewrite. Not changing the opencode-底座 commitment (session / SDK / providers / tools stay upstream).
When (not now). Proposed after #208 lands and the visual language settles ~2 weeks, and after the dynamic shell tabs series (Task 1-13 Codex handoff) lands. Batch with the next deliberate brand iteration so the swap piggybacks on a visible change instead of landing invisibly. Prevents thrashing the foundation layer while the design it supports is still settling.
Alternatives considered.
- Keep patching. Sustainable, but the tax compounds with every new chip surface. Current state.
- Full UI rewrite. 2-3 months, blocked by the opencode-底座 commitment (≤10K users, no 自研底座). Rejected.
- Wait for opencode upstream to match. Design has permanently diverged. Not coming back.
Open questions.
- Keep
@opencode-ai/ui/popover for non-chip popovers (sidebar context menus etc.) and only introduce a second primitive for chips, or replace the shared wrapper entirely? Probably the former — narrower scope.
<Chip> primitive in packages/ui/src/components/chip-pw.tsx (shared) or packages/app/src/components/prompt-input/chip.tsx (app-local)? Probably app-local first, promote if reused.
What task are you trying to do?
Ship UI work on the composer, chips, and popovers without paying a CSS-override tax every PR.
PawWork's design language has permanently diverged from opencode's (pill shapes, brand orange
#FF5910/#E56A2E,rounded-full/ radius-xl popovers, menu-below composer, customWorkspaceChip/SendButton), but the underlying primitives still come from@opencode-ai/ui(Kobalte wrapper +popover.css+ sharedButtonvariants). Every feature PR that touches chips or popovers pays a tax to override upstream styling, and most of the iteration rounds on #208 were spent fighting that tax rather than moving the product forward.What do you do today?
Patch case by case. From #208 alone:
dialog-select-model, prompt-input variant,workspace-chip) use inlinestyle={{ "border-radius": "16px" }}to beat[data-component="popover-content"] { border-radius: var(--radius-md) }. Tailwind!modifier and class both lose to source order.SendButtonuses custom#E56A2E+ inlinecolor: #ffffffon the Icon to beat[data-component="icon"] { color: var(--icon-base) }.as anycast on KobaltetriggerPropsto injectdata-actionattrs (Popover types don't acceptdata-*).hover:bg-background-base-hovertoken (silently dropped by Tailwind) — only visual review caught it.rounded-xl→rounded-2xl→rounded-fullacross three rounds because no shared chip primitive pinned the shape.None of these are bugs. They're all the same shape: brand-layer design colliding with a primitive layer that assumes a different design.
What would a good result look like?
Three focused replacements, each a self-contained sub-PR:
popover.css. Radius / background / border baked into Tailwind at the primitive layer. Migrate model / variant / workspace popovers off the shared@opencode-ai/ui/popoverwrapper.<Chip>primitive — unified trigger for Model / Variant / Workspace chips. Hover, focus, disabled, truncation in one place. Removes piggyback onButtonand removes the "why does this chip not hover like that chip" asymmetry.--pawwork-accent-brand,--pawwork-radius-chip,--pawwork-radius-popover,--pawwork-shadow-chip. Stop co-opting@opencode-ai/uitokens (--radius-md,--accent-brand) whose semantics live elsewhere.Acceptance criteria:
style={{ "border-radius": ... }}inprompt-input.tsx,workspace-chip.tsx,dialog-select-model.tsxas anycasts on Popover trigger props inpackages/app/src/componentsAudience
Both — this is developer-facing, but it directly improves the speed and quality of every UI iteration that reaches users. Visible payoff is faster, more consistent visual updates for everyone.
Extra context
Non-goals. Not rewriting sidebar / session list / command palette / tool-execution UI / permission dialogs / chat stream rendering. Not replacing
@opencode-ai/ui/buttonwholesale. Not a frontend rewrite. Not changing the opencode-底座 commitment (session / SDK / providers / tools stay upstream).When (not now). Proposed after #208 lands and the visual language settles ~2 weeks, and after the dynamic shell tabs series (Task 1-13 Codex handoff) lands. Batch with the next deliberate brand iteration so the swap piggybacks on a visible change instead of landing invisibly. Prevents thrashing the foundation layer while the design it supports is still settling.
Alternatives considered.
Open questions.
@opencode-ai/ui/popoverfor non-chip popovers (sidebar context menus etc.) and only introduce a second primitive for chips, or replace the shared wrapper entirely? Probably the former — narrower scope.<Chip>primitive inpackages/ui/src/components/chip-pw.tsx(shared) orpackages/app/src/components/prompt-input/chip.tsx(app-local)? Probably app-local first, promote if reused.