feat(plugin): bridge workspace adaptors into opencode#34
Merged
Conversation
2cead6f to
82e3f85
Compare
8 tasks
Astro-Han
added a commit
that referenced
this pull request
May 9, 2026
Commit 81aa587 (joined-card dock refactor) hardcoded dockProgress={1} on SessionTodoDock and removed the parent-side spring that drove it, so the dock popped in the moment props.state.dock() flipped true instead of sliding/fading in from 0. Restore the mount animation: - session-composer-region: re-introduce a useSpring tracking dockOpen, keep the segment mounted while it animates out (dockMounted memo), pass the spring value through as dockProgress. - session-todo-dock: multiply dock() into the max-height expression so the segment slides 0 → collapsed-height (or 0 → full when mounting in expanded state), in addition to the existing opacity drive that was already wired. Composer-dock spring options now appear in three places (Todo collapse, useDockCollapse for Followup/Revert, and segment mount). Extract them into a single DOCK_MOTION constant in composer/motion.ts so the contract is referenceable, not scattered. Pinning the value (0.3s, bounce:0) is the de facto standard across composer springs; hoisting to a global SPRING_TOKENS tier ladder and wiring spring through prefers-reduced-motion are tracked in #34.
Astro-Han
added a commit
that referenced
this pull request
May 9, 2026
Critical:
- dock-card: stop concatenating split.class into a single classList key.
"foo bar" was becoming a literal token and tripping
Element.classList.toggle (DOMException). Solid merges class+classList
natively when both are passed; restored to that idiom for DockCard,
DockSegment, and DockSegmentForm.
Major:
- editor-serialize: skip the block-separator newline when visit() has
already contributed one (e.g. an empty <div><br></div>). Fixes
multiline draft round-trips that were inflating "foo\n\nbar" into
"foo\n\n\nbar".
- model-picker: nested ThinkingLevel popover renders into Kobalte.Portal,
so its content sits outside the outer Content's DOM subtree. The
outer popover's onPointerDownOutside fired on every inner-popover
click and dismissed the model picker. Both popovers carry
data-picker-content; outer now ignores the dismiss when
event.target.closest("[data-picker-content]") matches a nested
picker.
- session-composer-region: permission requests can arrive while
prompt.ready() is still false (e.g. switching into a session whose
prompt is hydrating). The hydrate fallback used to show only the
loading card, leaving the user no way to approve or deny. The
fallback now renders SessionPermissionContent when a request is
pending and falls through to the loading card otherwise.
- send-button: SendButtonProps declared style?: JSX.CSSProperties |
string but the merge silently dropped the string branch. No caller
passes a string today, so narrow the prop type to JSX.CSSProperties
to match actual behavior instead of inventing a string-merge code
path.
Minor:
- keydown: the Enter-without-Shift branch had if (stopping())
handleSubmit / else handleSubmit — both arms identical, so the
conditional was dead. Inlined to a single handleSubmit call. The
stopping accessor stays in scope for the keydown probe.
- popover-controllers: include cmd.source in the custom slash command
id so workspace + user configs sharing a command name don't collapse
into one entry under useFilteredList.
Medium (gemini):
- use-dock-collapse: contentRef is now a signal driven by the JSX ref
callback, and createResizeObserver consumes that signal directly
rather than running inside a createEffect. Idiomatic and tracks
ref re-assignment correctly.
Push back:
- model-picker module-level externalOpen / openModelPicker(): the
signal is a deliberate imperative API consumed by use-session-commands
and the prompt-input keybind; multi-composer support would require
reshaping the call surface, not just the state. YAGNI for the single
composer this app has today; tracked under #34.
- editor-input draft carryover preserves text only: pre-existing
intentional design from PR #185 (commit ea01514 "carry plain-text
draft across directory switch"). Cross-workspace pill resolution
needs workspace-aware path semantics — out of slice 10 scope.
Astro-Han
added a commit
that referenced
this pull request
May 9, 2026
The composer-slice-10 spec asserted boundingBox 32px but the actual SendButton renders h-[30px] w-[30px] and DESIGN.md commits to the 30 dense tier. The assertion slipped through CI because it lacks the @smoke tag the smoke job filters on; it would have failed the moment someone ran the full e2e shard. Caught in external code review. Push back deferred: openModelPicker() module-level externalOpen as P3 follow-up — same reasoning as the prior round. The single-composer path cleans externalOpen on onOpenChange(false), and a multi-composer fix needs a mounted registry / focus-aware dispatch redesign rather than a state shape tweak. Tracked under issue #34.
Astro-Han
added a commit
that referenced
this pull request
May 9, 2026
Slice 10 of issue #440 — full composer + dock + model picker rewrite. 38 commits squashed. ## Highlights - **Composer joined-card shell**: new DockCard + DockSegment primitives in @opencode-ai/ui, dock widgets share the L34 joined-card layout. Hybrid composer shell renders permission UI inline. - **prompt-input.tsx 1602 → ~570 lines**: split into prompt-input/{store-types, editor-serialize, editor-imperatives, comment-routing, history-navigation, popover-controllers, editor-input, keydown, model-controls}. No behavior change — all factories injected via deps; popover↔editor-input cycle resolved via mutable forward-ref. - **DockWidgetHeader unified**: single 36px collapsed header with 30px chev IconButton (3+3 breathing) — consolidates Followup, Revert, Todo widgets. - **DOCK_MOTION contract**: composer/dock/widgets share `{ visualDuration: 0.3, bounce: 0 }` via packages/app/src/pages/session/composer/motion.ts. Aligns with global useSpring convention. - **Model picker rewrite**: variant control merged into model trigger; thinking-level promoted from separate popover to inline section. `[data-picker-content]` guard prevents nested popover dismiss. - **Send button + ContextUsage parity**: 30px outer / 16px inner glyph, theme-locked colors, larger stop glyph. - **Followup defaults to queue mode**: drops the legacy steer override. - **Control heights**: single 30px dense tier across composer; sidebar trailing actions step down to 26 per nesting rule. - **Picker contracts**: tighter padding, narrower width (340), dropped sticky labels. ## Post-review fixes (CI + 2 review rounds) - Critical: DockCard manual classList merge (single key with spaces) was throwing DOMException InvalidCharacterError — replaced with Solid native class+classList co-rendering. - CI typecheck: PopoverControllers types aligned with upstream solid-list `Accessor<string | null>` (was `undefined`). - CI e2e: dropped stale `prompt-variant-control` selector (variant folded into model picker). - Send button e2e contract: 32 → 30px to match dense tier. - Editor serialize: fixed double-newline regression for blank rows. - Keydown: removed dead `if (stopping())` branches (both arms identical). - Custom slash command id includes source to prevent workspace+user collision. - Model trigger label: pinned to `min-w-[80px] max-w-[180px]` to balance short names ("GPT-5.5") vs long ("DeepSeek V4 Pro") without jiggling the workspace control. ## Systemic finding (tracked under #34) PawWork sets `html { font: var(--type-body) }` → root font-size 13px. This means **Tailwind rem-based units render at 81% of their pixel-named values**: `h-9` = 29.25px, not 36px. Multiple visual bugs in this slice (chev clipping, header height) traced back to this. Remediation: use absolute pixels (`h-[36px]`) for grid-pinned heights; avoid Tailwind's pixel-named scale for 4pt-grid alignment. DESIGN.md docs debt to be filed under #34. ## Hand-test backlog (open after merge) - #32 dark theme regression sweep - #33 single-prompt E2E ## Files of note - packages/ui/src/components/dock-card.tsx (new) - packages/app/src/pages/session/composer/{motion.ts, use-dock-collapse.ts, dock-widget-header.tsx, session-composer-region.tsx, session-todo-dock.tsx} - packages/app/src/components/prompt-input/* (9 new files) - packages/app/e2e/composer/composer-slice-10.spec.ts (new) Closes part of #440.
11 tasks
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
Why
Issue #27 needs a runtime bridge so plugin-defined workspace adaptors keep working after disposal, checkout switches, and cold starts. Before this slice, adaptor registration was effectively instance-local, persisted workspaces could resolve to the wrong checkout, upgraded rows with missing owner metadata had fragile recovery, and router-based cold starts could serve a request without actually restoring background sync.
Related Issue
Part of #27
How To Verify
bun test test/plugin/workspace-adaptor.test.ts test/plugin/loader-shared.test.ts test/plugin/meta.test.ts test/plugin/trigger.test.ts test/project/project.test.ts test/project/worktree.test.ts test/project/worktree-remove.test.ts test/server/workspace-router.test.ts test/plugin/github-copilot-models.test.ts --timeout 30000 bun run typecheck bun run script/check-migrations.ts bun --cwd packages/plugin run typecheck bun turbo typecheckManual checks: none, no visible UI changes in this slice.
Screenshots or Recordings
Not applicable, no visible UI changes.
Checklist
devbranch