refactor(ui): decompose composer and plan-reminder panels into focused hooks (#1044) - #1153
Merged
Merged
Conversation
Pull the per-session draft store (draftStoreRef/activeDraftKeyRef/
hasDraftText + the draftKey swap effect) into useComposerDraft, and the
up/down prompt-history navigation state machine into useComposerHistory.
The pure store/navigation logic stays in composer-helpers.ts; the hooks
are the React seam that applies results to the uncontrolled textarea.
sendCurrent now clears the submitted key via clearDraft(submittedDraftKey)
+ saveCurrentDraft(''); the composer-send-guard contract is co-migrated to
pin the same invariant across the composer/hook seam.
Move the @// mention-popup state machine (trigger detection, items, active index, post-insertion suppression, selectionchange listener) into useMentionPopup; Composer keeps only the keydown routing, driving the hook through identically-named bindings so the mention contract reads the same source shape. Move the workspace + branch picker row into ComposerWorkspaceRow with named picker prop types; project-context-badge is co-migrated to pin the same invariants across the composer/row seam, and the copy-hygiene + stub-vocabulary scans follow the moved file. Also drops two imports that became dead with the move (MenuSeparator, FileEdit).
The create/edit dialog now owns all nine form field states, editingId,
the submitPending single-flight owner, validation, and the close guard;
the panel keeps only list/runs/query state, per-action pending, and
refresh. The panel opens the dialog with a PlanReminderFormSeed and
remounts it per open (key={formNonce}), so fields initialize from the
seed exactly where the old open-handler setters put them. The shared
PlanReminderSelect moves to its own leaf module used by both surfaces.
Co-migrated contracts pin the same invariants across the seam:
plan-reminder-panel-contract (submit owner/close guard now asserted on
the dialog), command-palette (title-input marker), utility-primitives
(preset button variants), and the copy-hygiene/stub-vocabulary scans.
One deliberate simplification: the card-menu edit/duplicate items drop
their submitPending || guard — unreachable, since the menu lives behind
the modal dialog whenever a submit can be in flight.
Astro-Han
added a commit
that referenced
this pull request
Jul 17, 2026
…llow-up) (#1157) * test(ui): cover composer draft and history pure helpers Pin rememberComposerDraft/readComposerDraft (key no-op, blank delete, 120k/32 caps), rememberComposerHistoryEntry (trim/dedup/50 cap), and navigateComposerHistory (up/down + saved-draft round trip) so the useComposerDraft/useComposerHistory seams from #1153 stay pure-unit covered. * refactor(ui): move plan-reminder form seeds into helpers PlanReminderFormSeed and the create/template/edit/duplicate seed builders are pure mappings — relocate them next to the other form helpers so PlanReminderFormDialog stays the React owner of field state and submit, matching the #1044 ownership split.
23 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
Closes #1044.
Behavior- and visually-neutral decomposition of the two shared-UI panels so each hook/component owns one concern:
Composer (
composer.tsx1121 → 787 lines) — now orchestrates only:use-composer-draft.ts— per-session draft persistence: the bounded draft Map, active key,hasDraftText,saveCurrentDraft/clearDraft, and thedraftKeyswap effect (pure store ops stay incomposer-helpers.ts).use-composer-history.ts— up/down prompt-history navigation: the in-memory history mirror,resetNavigation/rememberSentEntry, and the arrow-key handler that applies navigation to the uncontrolled textarea.use-mention-popup.ts— the@//mention state machine: trigger detection, items, active index, post-insertion suppression,selectionchangelistener. Composer keeps only the keydown routing, driving the hook through identically-named bindings.composer-workspace-row.tsx— the workspace + branch picker row, with namedComposerWorkspacePicker/ComposerBranchPickerprop types.Plan reminder (
plan-reminder-panel.tsx863 → 540 lines) — the panel keeps only list/runs/query state, per-action pending, and refresh:plan-reminder-form-dialog.tsx—PlanReminderFormDialogowns ALL create/edit form state (nine fields,editingId), thesubmitPendingsingle-flight owner, validation, and the submit/close pipeline. The panel opens it with aPlanReminderFormSeedand remounts per open (key={formNonce}), so fields initialize from the seed exactly where the old open-handler setters put them.plan-reminder-select.tsx— the sharedPlanReminderSelectused by both surfaces.Public export surface is unchanged (
Composer/ComposerHandleviacomponents.tsx,PlanReminderPanelviaplan-reminder-panel.tsx);@maka/uistays host-agnostic (no new props, nowindow.maka).Contract tests that regex-match raw source were co-migrated to pin the same invariants across the new seams — none weakened:
composer-send-guard(draft internals now asserted on the hook; send path unchanged),project-context-badge(workspace-row assertions on the row component),plan-reminder-panel-contract(submit owner/close guard asserted on the dialog),command-palette-plan-reminder(title-input marker),renderer-utility-primitives(preset button variants), and the copy-hygiene / stub-vocabulary scans now include the new files.One deliberate simplification: the card-menu edit/duplicate items drop their
submitPending ||disabled guard — unreachable, since the menu lives behind the modal dialog whenever a submit can be in flight. Also dropped two imports incomposer.tsxthat the move made dead (MenuSeparator,FileEdit).Verification
npm run test:fastfrom the worktree root — all workspace tests pass (includesbuild:test; ui + desktop suites green end to end).composer-send-guard,composer-mention,composer-esc-drag-clear,composer-constant-footprint,permission-composer-takeover, SSR hint tests) and all plan-reminder tests (95 tests incl.plan-reminder-panel-contract,tab-spec-499,badge/chip-converge) — green after each commit.npm run typecheck(all workspaces, incl. desktop renderer + storybook tsconfigs) — clean.npm run lint— clean.Review focus
The riskiest extraction is the draft-persistence hook (
use-composer-draft.ts): thedraftKeyswap effect must remember the outgoing value under the OLD key, swap in the NEW key's draft, and reset history navigation in one pass — including the first-send-new-session re-key, wheresendCurrentclears the submitted key viaclearDraft(submittedDraftKey)+saveCurrentDraft(''). The send-guard contract pins this path; worth a manual read of the hook's effect against the old inline code.