# Component Reuse & Catalog (https://jackin.tailrocks.com/reference/tui/components/)



## Component Reuse — Hard Rule [#component-reuse--hard-rule]

**Never copy-paste a TUI component. Extend or compose instead.** This is the single most important architectural rule for the jackin' TUI.

### Core rules [#core-rules]

1. **Every visual pattern that appears in more than one place must use one shared implementation.** A single widget, render function, or helper — not two near-copies. When the existing implementation cannot serve a new call site without modification, extend it (add a parameter, generalize a branch, add an enum variant) rather than forking it.

2. **Components have two canonical homes.** Cross-surface components live in `crates/jackin-tui/src/components/`. Surface-local components live in `crates/<surface-crate>/src/tui/components/`. The remaining root-console op-picker adapter in `src/console/tui/op_picker/` is transitional until migrated; use it when extending current op-picker behavior, but do not add new canonical component patterns there.

3. **A genuinely new component is only ever written once.** The first time a new UX pattern appears, it is acceptable to implement it in the most focused way that serves the immediate need. The second time the same pattern is needed somewhere else, the two implementations must be consolidated into one shared widget before the second PR lands — not "as a follow-up."

4. **Copy-paste for convenience is a blocker.** When a PR produces a second near-copy of an existing widget or render function — even 90% similar, even with minor additions — that is a TUI design-decision violation. Reviewers block merge; the fix is consolidation, not a comment.

5. **Refactoring to enable reuse is not optional.** If an existing component does not yet accept a parameter that a new call site needs, add the parameter. If an existing component mixes two responsibilities that need to be separated, separate them. The cost of a targeted refactor is always lower than the cost of maintaining two divergent implementations through every future bug fix and enhancement.

6. **Settings screens mirror workspace screens.** Settings surfaces that intentionally parallel workspace editor screens must reuse the workspace widgets and flow helpers wherever behavior is the same; keep separate code only for the different persistence target or config scope. Visual drift between the two is a bug.

7. **Component APIs follow the TUI boundary.** Component props/state/messages may describe UI state and emit semantic outcomes. They must not own Docker/git/`op`/`gh` calls, config persistence, workspace resolution, or protocol authority. When a component needs work done, it emits an outcome/message that update turns into a typed effect.

### What to check before writing new code [#what-to-check-before-writing-new-code]

Before adding any new TUI widget or state type:

1. Search the shared component crate first: `rg 'struct .*State|fn render_|impl Widget|impl StatefulWidget' crates/jackin-tui/src/components crates/jackin-tui/src`.
2. Search the owning surface's final local component area: `rg 'struct .*State|fn render_|impl Widget|impl StatefulWidget' crates/<surface-crate>/src/tui`.
3. While the console migration is incomplete, also search root-console TUI adapters before adding or moving anything: `rg 'struct .*State|fn render_' src/console/tui`.
4. If a component already owns the pattern, extend it. Add a mode enum, parameter, flag, or semantic variant. Do not create a second module.
5. If no component owns the pattern and only one surface needs it, add it under `crates/<surface-crate>/src/tui/components/` with a thin `components.rs` export. If more than one surface needs it, add it under `crates/jackin-tui/src/components/` with a lookbook story and visual regression coverage.
6. When moving an existing component into the final structure, preserve behavior first: identify existing tests/snapshots or add focused coverage, move the code, keep compatibility shims only where needed for the migration, and verify the same keyboard, mouse, focus, footer, error, and render behavior afterward.

### Why this rule exists — canonical lesson [#why-this-rule-exists--canonical-lesson]

A `token_store_picker` widget once duplicated the Account → Vault → Item navigation state machine from <RepoFile path="crates/jackin-console/src/tui/op_picker.rs">crates/jackin-console/src/tui/op\_picker.rs</RepoFile> — a 1067-line copy of closely parallel code where every `op_picker` bug fix to navigation, filtering, R-key refresh, or section display had to be manually reproduced or the two implementations drifted. That copy has since been deleted and `op_picker` was extended instead: an opt-in `OpPickerMode::Create` turns on the `+ New item`, `+ New field`, and `+ New section` creation sentinels and the naming sub-stages, and the picker commits an `OpPickerSelection` enum (`Existing` / `NewItem` / `EditItemField`). One widget now serves both the browse and create call sites. This is the positive reference for the rule: when a new call site needs more, **extend the shared widget with a mode**, never fork it into a second drill-down. The remaining Claude-token work is tracked in the [Workspace Claude Token Setup](/reference/roadmap/workspace-claude-token-setup/) roadmap item.

***

## Reusable Component Catalog [#reusable-component-catalog]

Reusable TUI widgets live first in `crates/jackin-tui/src/components/`. Surface-local widgets should live under the owning surface's `src/tui/components/`. The root console still has the transitional op-picker adapter in `src/console/tui/op_picker/`; it remains the valid current owner until migrated, but that path is not the target structure for new canonical component work. Use this table to identify the right component before writing new code or moving an existing widget.

| Component                        | Module                                                                                                                                                                                                                                                                              | What it provides                                                                                                                                                                                                                                                                                                                                      | When to use                                                                                              |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| Op picker (1Password drill-down) | <RepoFile path="crates/jackin-console/src/tui/op_picker.rs">crates/jackin-console/src/tui/op\_picker.rs</RepoFile>                                                                                                                                                                  | Account → Vault → Item → Field 4-stage drill-down with filter, R-key refresh, collapsible section headers, background loading, and `op://` reference commit. An opt-in `OpPickerMode::Create` adds `+ New item` / `+ New field` / `+ New section` creation sentinels and naming sub-stages, committing an `OpPickerSelection` enum                    | Any flow that needs to browse, pick, or create a 1Password field reference                               |
| Role picker                      | <RepoFile path="crates/jackin-console/src/tui/components/role_picker.rs">crates/jackin-console/src/tui/components/role\_picker.rs</RepoFile>                                                                                                                                        | Filter list of available roles with type-to-narrow and Enter commit                                                                                                                                                                                                                                                                                   | Any flow that needs to select a role from the known set                                                  |
| Text input box                   | <RepoFile path="crates/jackin-tui/src/components/text_input.rs">crates/jackin-tui/src/components/text\_input.rs</RepoFile>                                                                                                                                                          | Single-line labelled input box (`TextInputState` + `render_text_input`), shared prompt geometry (`text_input_prompt_rect`), and the titled/labelled variant (`render_labeled_text_input_dialog&#x60;) with validation, duplicate detection, forbidden-char rules. This is the **"Credential" dialog** the operator sees when typing any single value. | Any flow needing one line of free text — env value, item name, field label, section name, workspace name |
| Scope picker                     | <RepoFile path="crates/jackin-console/src/tui/components/scope_picker.rs">crates/jackin-console/src/tui/components/scope\_picker.rs</RepoFile>                                                                                                                                      | Two-button horizontal strip: `All roles` / `Specific role`                                                                                                                                                                                                                                                                                            | Any "apply to everything vs apply to one" choice                                                         |
| Source picker                    | <RepoFile path="crates/jackin-console/src/tui/components/source_picker.rs">crates/jackin-console/src/tui/components/source\_picker.rs</RepoFile>                                                                                                                                    | Two-button horizontal strip: `Plain text` / `1Password`                                                                                                                                                                                                                                                                                               | Choosing whether a value is literal or an `op://` reference                                              |
| Agent choice                     | <RepoFile path="crates/jackin-console/src/tui/components/agent_choice.rs">crates/jackin-console/src/tui/components/agent\_choice.rs</RepoFile>                                                                                                                                      | Two-button agent selector used in the Auth-tab `+ Add` flow                                                                                                                                                                                                                                                                                           | Picking one agent from a small fixed set                                                                 |
| Mount destination choice         | <RepoFile path="crates/jackin-console/src/tui/components/mount_dst_choice.rs">crates/jackin-console/src/tui/components/mount\_dst\_choice.rs</RepoFile>                                                                                                                             | Two-button strip for mount destination choices                                                                                                                                                                                                                                                                                                        | Mount configuration flows                                                                                |
| Workdir picker                   | <RepoFile path="crates/jackin-console/src/tui/components/workdir_pick.rs">crates/jackin-console/src/tui/components/workdir\_pick.rs</RepoFile>                                                                                                                                      | Filtered list of mounted workspace paths                                                                                                                                                                                                                                                                                                              | Mount configuration flows that reuse an existing workspace path                                          |
| Confirm dialog                   | <RepoFile path="crates/jackin-tui/src/components/confirm_dialog.rs">crates/jackin-tui/src/components/confirm\_dialog.rs</RepoFile>                                                                                                                                                  | Yes/No two-button confirm with the canonical layout (see Confirmation dialogs rule), plus generic structured detail rows and note rows for prompts that need extra context. Product-specific strings are assembled by the owning surface, not by `jackin-tui`.                                                                                        | Any destructive or irreversible action                                                                   |
| Save / discard strip             | <RepoFile path="crates/jackin-tui/src/components/save_discard_dialog.rs">crates/jackin-tui/src/components/save\_discard\_dialog.rs</RepoFile>                                                                                                                                       | Save / Discard two-button strip, designed for form footers                                                                                                                                                                                                                                                                                            | Bottom of any editable form                                                                              |
| Toast                            | <RepoFile path="crates/jackin-tui/src/components/toast.rs">crates/jackin-tui/src/components/toast.rs</RepoFile>                                                                                                                                                                     | Non-blocking, auto-expiring overlay for state feedback such as `Selection copied`, with caller-supplied reserved rows so it never replaces footer/action hints                                                                                                                                                                                        | Copy-success or similar transient feedback that must appear outside the hint/footer row                  |
| File browser                     | <RepoFile path="crates/jackin-console/src/tui/components/file_browser.rs">crates/jackin-console/src/tui/components/file\_browser.rs</RepoFile> + <RepoFile path="crates/jackin-console/src/services/file_browser.rs">crates/jackin-console/src/services/file\_browser.rs</RepoFile> | Host filesystem navigation UI; navigation, commit validation, and Git URL lookup leave as typed outcomes for non-TUI adapters; browser-open queues `ManagerEffect::OpenUrl` for the root effect executor                                                                                                                                              | Any flow needing to pick a local file or directory                                                       |
| GitHub picker                    | <RepoFile path="crates/jackin-console/src/tui/components/github_picker.rs">crates/jackin-console/src/tui/components/github\_picker.rs</RepoFile>                                                                                                                                    | GitHub org/repo drill-down                                                                                                                                                                                                                                                                                                                            | Any flow needing to select a GitHub repository                                                           |
| Error popup                      | <RepoFile path="crates/jackin-tui/src/components/error_dialog.rs">crates/jackin-tui/src/components/error\_dialog.rs</RepoFile>                                                                                                                                                      | Scrollable red-border error modal (see Error Surface rule)                                                                                                                                                                                                                                                                                            | All error display                                                                                        |
| Panel rain                       | Background rain animation                                                                                                                                                                                                                                                           | Background decoration                                                                                                                                                                                                                                                                                                                                 |                                                                                                          |

Console role-load error text is assembled through <RepoFile path="crates/jackin-console/src/tui/components/error_popup.rs">crates/jackin-console/src/tui/components/error\_popup.rs</RepoFile>. Root state may classify root-only repository and validation error types, but popup wording belongs to the console TUI component boundary.

Console effect executors use the same error-popup helper boundary for side-effect failure titles such as token generation, URL opening, workspace deletion, and file-browser startup. The executor owns the work and the concrete error value; <RepoFile path="crates/jackin-console/src/tui/components/error_popup.rs">crates/jackin-console/src/tui/components/error\_popup.rs</RepoFile> owns the visible popup title/state construction.

Background-work status popups follow the same split: <RepoFile path="crates/jackin/src/console/effects.rs">crates/jackin/src/console/effects.rs</RepoFile> starts role registration, workspace drift checks, and isolated-state cleanup, while <RepoFile path="crates/jackin-console/src/tui/components/status_popup.rs">crates/jackin-console/src/tui/components/status\_popup.rs</RepoFile> owns the visible status title/message construction.

Root-console tests should seed modals through the same crate-owned helpers as runtime whenever the helper exists. This keeps the final-structure audit honest: direct constructors in root tests are treated as drift unless the test is explicitly exercising the shared primitive itself.

Test-only helpers that need command runners, service calls, or config persistence belong with effect/service adapters, not input handlers. Role-input tests may use fake runners through <RepoFile path="crates/jackin/src/console/effects.rs">crates/jackin/src/console/effects.rs</RepoFile>, but <RepoFile path="crates/jackin-console/src/tui/input/editor.rs">crates/jackin-console/src/tui/input/editor.rs</RepoFile> must stay limited to input-to-outcome routing and modal state transitions.

The transitional root op-picker adapter may own `operator_env` types, runner wiring, cache binding, subscriptions, and crossterm-to-outcome mapping, but user-visible picker copy, load-error state shaping, and blocked-load key policy belong in <RepoFile path="crates/jackin-console/src/tui/components/op_picker.rs">crates/jackin-console/src/tui/components/op\_picker.rs</RepoFile> with the rest of the component. Root adapters pass concrete process errors, subscription-closed facts, or `Esc`/not-`Esc` key facts into component helpers; they do not construct recoverable/fatal picker error states inline or duplicate the loading/fatal key-consumption rule.

Editor save prompts and save-blocking error text that depend only on visible editor facts live with editor screen view helpers in <RepoFile path="crates/jackin-console/src/tui/screens/editor/view.rs">crates/jackin-console/src/tui/screens/editor/view\.rs</RepoFile>. Root save input and the effect executor still own drift detection, preserved-state cleanup, and workspace writes; they pass render-safe facts such as affected container names into the screen helpers.

Editor General-tab row vocabulary and content-width planning also live with editor screen view helpers in <RepoFile path="crates/jackin-console/src/tui/screens/editor/view.rs">crates/jackin-console/src/tui/screens/editor/view\.rs</RepoFile>. Root layout code may shorten paths and pass current booleans, but it must not duplicate the row labels or enabled/disabled wording used by rendering.

Editor header title wording lives with editor screen view helpers in <RepoFile path="crates/jackin-console/src/tui/screens/editor/view.rs">crates/jackin-console/src/tui/screens/editor/view\.rs</RepoFile>. Root rendering supplies only whether the editor is creating or editing and, for edit mode, the workspace name.

Settings header title wording lives with settings screen view helpers in <RepoFile path="crates/jackin-console/src/tui/screens/settings/view.rs">crates/jackin-console/src/tui/screens/settings/view\.rs</RepoFile>. Root rendering should ask that helper for the screen title rather than carrying settings-screen copy.

Workspace-list header title wording lives with top-level console view helpers in <RepoFile path="crates/jackin-console/src/tui/view.rs">crates/jackin-console/src/tui/view\.rs</RepoFile>. Root frame rendering should ask that helper for the workspace screen title rather than carrying workspace-screen copy.

Editor/settings save-footer base labels and pick-list modal commit labels live with console-local footer hint components in <RepoFile path="crates/jackin-console/src/tui/components/footer_hints.rs">crates/jackin-console/src/tui/components/footer\_hints.rs</RepoFile>. Root footer adapters may pass dirty/change facts, row modes, and modal variants, but they should not carry the `save workspace`, `save settings`, `select`, or `confirm` copy.

Generated-token source-picker label construction lives with the auth-panel component in <RepoFile path="crates/jackin-console/src/tui/components/auth_panel.rs">crates/jackin-console/src/tui/components/auth\_panel.rs</RepoFile>. Editor and settings input code decides when token generation starts, but visible source-picker state comes from the component helper.

Generated-token 1Password item-name default construction also lives with the auth-panel component in <RepoFile path="crates/jackin-console/src/tui/components/auth_panel.rs">crates/jackin-console/src/tui/components/auth\_panel.rs</RepoFile>. Editor and settings token-generation input code should pass the template and scope label into the helper rather than hard-coding `{ws}` replacement in root input handlers.

#### Lookbook focus behavior [#lookbook-focus-behavior]

`jackin-tui-lookbook` (`cargo run -p jackin-tui-lookbook -- --terminal`) follows the same focus model as every other jackin' surface:

* **Sidebar / story list** — focus starts here on launch; the sidebar panel shows a `PHOSPHOR_GREEN` border; `↑`/`↓` navigate stories; `Tab` or a click on the preview panel transfers focus to the preview.
* **Preview panel** — shows a `PHOSPHOR_GREEN` border when focused; `Esc`/`Tab`/`⇧` returns focus to the sidebar; keyboard events route to the active component interactor inside the preview.

The lookbook is the visual regression surface for every component in the catalog — ensure lookbook stories for new components exercise their focused and unfocused states so color regressions are caught before they reach production.

**Render primitives**: `modal_block` (<RepoFile path="crates/jackin-tui/src/components/panel.rs">crates/jackin-tui/src/components/panel.rs</RepoFile>), `breadcrumb_title` (<RepoFile path="crates/jackin-console/src/tui/components/op_picker.rs">crates/jackin-console/src/tui/components/op\_picker.rs</RepoFile>), `render_fatal` (<RepoFile path="crates/jackin-console/src/tui/components/op_picker.rs">crates/jackin-console/src/tui/components/op\_picker.rs</RepoFile>), `render_filter_input` (<RepoFile path="crates/jackin-tui/src/components/filter_input.rs">crates/jackin-tui/src/components/filter\_input.rs</RepoFile>), `render_picker_list` (<RepoFile path="crates/jackin-tui/src/components/select_list.rs">crates/jackin-tui/src/components/select\_list.rs</RepoFile>), and `render_scrollable_block_at` (<RepoFile path="crates/jackin-tui/src/components/scrollable_panel.rs">crates/jackin-tui/src/components/scrollable\_panel.rs</RepoFile>) — use these building blocks when composing new modals rather than hand-rolling border + layout from scratch.

### One picker-list renderer, with full-width section dividers [#one-picker-list-renderer-with-full-width-section-dividers]

Every modal list — the host console pickers and the in-container capsule menu/pickers — draws its rows through the shared picker renderer in `select_list` (<RepoFile path="crates/jackin-tui/src/components/select_list.rs">crates/jackin-tui/src/components/select\_list.rs</RepoFile>). Full modal pickers use `render_picker_list` with `PickerRow` values; richer host pickers that own their own filter/state flow must still pass neutral, unselected `Line` rows to `render_picker_lines`. Callers must not pre-style selection, paint their own `▸` cursor, or stop the selected background at the text width. The shared renderer owns the selected-row gutter, full-width highlight, selection-follow viewport, and the rule that scrollbar gutter cells win over highlight cells.

There are exactly two full-picker row kinds, and a caller must not hand-roll either:

* `PickerRow::Item` — a selectable row. The selected row gets the canonical highlight (`PHOSPHOR_GREEN` background, black bold text, `▸ ` cursor) in a reserved 2-column gutter, so item labels align whether or not they are selected.
* `PickerRow::Separator(label)` — a non-selectable `──── label ────` section divider. &#x2A;*A section divider must span the full inner width, border to border, with the label centered.** `render_picker_list` repaints separator rows edge-to-edge *over* the selection gutter so the dashes reach both borders symmetrically — the divider is the one row that deliberately ignores the gutter. Dashes are `PHOSPHOR_DARK`, the label is dim. A divider that is indented on one side (gutter-shifted), shorter than the inner width, or whose label is not centered is a violation. Pass the bare label text (`"agents"`, `"shells"`) — never pre-build the dashes at the call site, or the width math drifts from the gutter.

### One scrollbar renderer [#one-scrollbar-renderer]

**Every scrollbar in jackin' — host console panels, dialogs, and capsule pane scrollback — must render through the shared `scrollable_panel` functions (<RepoFile path="crates/jackin-tui/src/components/scrollable_panel.rs">crates/jackin-tui/src/components/scrollable\_panel.rs</RepoFile>): `render_vertical_scrollbar*` / `render_horizontal_scrollbar`, `ScrollbarStyle::Line` (`┃`) as the vertical thumb, `SCROLLBAR_TRACK` (`·`) as the track, and the shared `DIALOG_SCROLL_THUMB` / `DIALOG_SCROLL_TRACK` colors. A hand-painted thumb loop (a `█` column, a custom glyph, or per-surface colors) is a review-blocking violation** — the same rule as the one picker-list renderer above. Tail-relative surfaces (capsule pane scrollback, where offset `0` means "live tail") bridge to the component's top-relative offsets with `jackin_tui::scroll::TailScroll::to_top_offset` over a content length of `filled + viewport`, and map track clicks back through `scrollbar_offset_for_track_position` so click-to-jump and the painted thumb share one geometry.

### Two-button choice pattern [#two-button-choice-pattern]

`scope_picker.rs`, `source_picker.rs`, `agent_choice.rs`, `mount_dst_choice.rs`, and `confirm.rs` are all the **same visual shape**: a small modal with two side-by-side buttons, `←/→` to move, `Enter` to select, `Esc` to cancel. When a step requires the operator to pick one of exactly two mutually-exclusive options, reuse one of these or model a new one on the identical shape — never invent a third layout. The operator must recognise "this is a two-way choice" instantly from the shape alone.

### One input-box dialog for every single-value prompt [#one-input-box-dialog-for-every-single-value-prompt]

**Every "ask the operator to type one value" prompt MUST use the shared `text_input` dialog (<RepoFile path="crates/jackin-tui/src/components/text_input.rs">crates/jackin-tui/src/components/text\_input.rs</RepoFile> — `TextInputState` + `render_text_input`, or `render_labeled_text_input_dialog` when the dialog title and field label are distinct).** That render fn draws the whole dialog: a single bordered box, the label/title, the input row, the dim input band, and the cursor styling. It is the box behind "New global environment key", the credential entry, the launch credential prompt, the workspace-name prompt, capsule `Rename tab`, and the op\_picker Create-mode naming stages. Do not hand-roll a second input box, and do not wrap `text_input::render_text_input` inside another bordered block (that produces a box-inside-a-box). The dialog is a single box sized by the shared text-input rect helper for that surface, with the footer showing the active commit/cancel hints — supplied by the modal footer, never drawn inside the box (see "Hints: footer only"). When a flow that already owns a larger modal (like the op\_picker drill-down) needs a one-value sub-prompt, render the plain `text_input` box at the text-input rect for that sub-stage rather than nesting it in the parent frame.

### Default values are pre-filled in input boxes [#default-values-are-pre-filled-in-input-boxes]

**When an input box has a suggested default, that default MUST be pre-filled into the box (cursor at end), not shown as placeholder/ghost text.** The operator either presses `Enter` to accept the pre-filled value or edits it. Never make the operator type a value that the system already knows the default for. This matches the CLI prompt convention (`dialoguer::Input::default(...)`, where `Enter` accepts the suggestion) so the two surfaces behave identically. Construct the input with the default as its initial content — `TextInputState::new(label, default)` already does this (the textarea opens holding `default` with the cursor at the end). Use an empty initial value (`""`) only when there is genuinely no sensible default (e.g. a new section name). Example: the op\_picker Create-mode naming stages open with `Claude` (item) and `oauth-token` (field) pre-filled; the operator hits `Enter` to take them or types over them.

### Toasts are state feedback, never action hints [#toasts-are-state-feedback-never-action-hints]

Transient state feedback such as `Selection copied` uses the shared `Toast` overlay (<RepoFile path="crates/jackin-tui/src/components/toast.rs">crates/jackin-tui/src/components/toast.rs</RepoFile>). A toast is non-focusable, appears above the caller's reserved footer/status rows, and expires through the owning surface's state timer. Do not put copy-success or other state feedback in the hint/footer row: that row is reserved for actions currently available in the focused area.

### Creation-sentinel pattern (`+ Add X` / `+ New X`) [#creation-sentinel-pattern--add-x---new-x]

jackin' has **one** convention for "add a new thing to this list": a sentinel row rendered at the bottom of the list with a leading `+ ` and the action label. It is the same across every surface:

* `+ New workspace` — list stage (<RepoFile path="crates/jackin-console/src/tui/screens/workspaces/view/list.rs">crates/jackin-console/src/tui/screens/workspaces/view/list.rs</RepoFile>)
* `+ Add role` — workspace editor Roles tab (<RepoFile path="crates/jackin-console/src/tui/screens/editor/view.rs">crates/jackin-console/src/tui/screens/editor/view\.rs</RepoFile>)
* `+ Add mount`, `+ Add environment variable`, `+ Add {role} environment variable` — settings/editor mount + env tabs (<RepoFile path="crates/jackin-console/src/tui/screens/settings/view.rs">crates/jackin-console/src/tui/screens/settings/view\.rs</RepoFile>)
* `+ Override for a role` — Auth tab per-role override

Selecting a creation sentinel opens the next step in the flow — usually the **text input box** to name the new thing, or a two-button choice. Any new "add an item to this list" affordance MUST render as a `+ ` sentinel row in the same position and style, and MUST route its selection through the text input box (for naming) or a picker (for choosing). Do not invent a different add-affordance (no separate "New…" button floating elsewhere, no key-only shortcut without a visible row).

***

## Wizard-Style Dialog Flows [#wizard-style-dialog-flows]

When a feature requires the operator to complete several sequential decisions before committing (pick a scope, pick a role, pick a source, select a credential location), implement it as a **wizard flow**: a chain of modals where each step is handled by a reusable component and `Esc` walks back one step.

### Rules [#rules]

* Each step is one reusable component from the catalog above. Do not design a custom combined-step UI.
* Modal stack discipline applies (see Sub-dialogs push onto a stack). Each step pushes onto the modal stack; `Esc` pops one step; a terminal commit clears the whole chain.
* The first step in the chain is opened from whatever UI element the operator interacts with (a button, a hint, an auth-tab action). It does not require a CLI flag.
* The flow lives in the TUI, not in the CLI. The CLI is for scripted or non-interactive invocations. The TUI is for interactive flows.

### Canonical example — Claude token setup flow [#canonical-example--claude-token-setup-flow]

This is **the reference example** for how to design any multi-step TUI flow in jackin', and it is **shipped**. Every screen reuses a component from the catalog above; the flow adds **zero new widgets** and only **extends** `op_picker` (`OpPickerMode::Create`). Read this whenever you design a new flow and ask "which existing component is each of my screens?" before writing any code.

The flow configures a Claude OAuth token, launched entirely from inside `jackin console` — the operator never leaves the console and never runs a CLI command.

**Scope comes from the auth form, not a picker step.** The operator chooses *what* the token wires by opening the right Claude `oauth_token` auth form: the workspace-level form (all roles), a per-role override form (reached via `+ Override for a role`), or the global form in Settings. There is deliberately no scope/role picker *inside* the generate flow — the auth tab's existing structure is the scope chooser, so the flow needs no `scope_picker`/`role_picker` step.

**Entry point — `G` on the Edit auth dialog.** With the form on `Mode: oauth_token`, `G` (shown in the footer as `G generate`) starts the flow. The screens, each mapped to the component it reuses:

| Step                        | Screen                                                     | Component reused                   | Notes                                                                                                                          |
| --------------------------- | ---------------------------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| 1                           | **Source**: `Plain text` / `1Password`                     | Source picker (`source_picker.rs`) | The same two-button dialog the provide flow uses.                                                                              |
| 2a &#x2A;(if "Plain text")* | *(no screen)*                                              | —                                  | The token is minted by `claude setup-token` and stored as a literal at the scope; nothing is typed.                            |
| 2b &#x2A;(if "1Password")*  | **Account → Vault → Item → \[Section →] Field** drill-down | `op_picker` (Create mode)          | The existing `op_picker`, reused for the drill-down; `OpPickerMode::Create` adds the Section stage + creation sentinels below. |

After the source/location choice, the console suspends, runs the `claude setup-token` OAuth capture, writes the minted token, and resumes — the storage location was already chosen, so the mint is the only blocking step.

**Step 2b in detail — extending `op_picker`, not copying it.** The 1Password branch reuses the existing `op_picker` Account, Vault, and Item screens exactly as they render today. Create mode adds a **Section stage** for an existing item and creation sentinels, each following the `+ Add X` pattern (a `+ ` row at the bottom of the list):

* **Item stage** — a `+ New item` sentinel below the item list. Selecting it opens the **text input box** to name the new item, then names the field — the token is written there.
* **Section stage** (existing item only) — rows are `(root)` (unsectioned fields), each existing named section, and a `+ New section` sentinel. Picking a section scopes the next screen; `+ New section` opens the **text input box** to name it.
* **Field stage** (scoped to the chosen section) — the existing fields in that section, plus a `+ New field` sentinel that opens the **text input box** to name the field.
* Selecting an **existing field** writes the token into that field in-place (overwrite); creation rows write into the chosen section (`(root)` ⇒ unsectioned). Browse mode skips the Section stage and keeps the flat field list with collapsible section headers.

The root console run loop that drives the wizard lives under <RepoFile path="crates/jackin/src/console/tui/run.rs">crates/jackin/src/console/tui/run.rs</RepoFile>; it suspends and resumes the terminal while external minting and write validation run through non-TUI services.

Every "create" affordance in this branch is the same `+ ` sentinel row, and every "name it" prompt is the same text input box. Nothing new is drawn.

**Implementation contract.** These sentinels are shipped in `op_picker` behind the opt-in `OpPickerMode::Create` mode parameter that turns the creation rows on; the picker commits an `OpPickerSelection` enum (`Existing` / `NewItem` / `EditItemField`) instead of only the plain `op://` reference string. This capability now lives in the shared <RepoFile path="crates/jackin-console/src/tui/op_picker.rs">crates/jackin-console/src/tui/op\_picker.rs</RepoFile> — it MUST NOT be reimplemented by forking `op_picker` into a second module.

### Rich TUI lives in the console; the CLI stays plain CLI [#rich-tui-lives-in-the-console-the-cli-stays-plain-cli]

There are two distinct interactive surfaces, and the established principle keeps them apart: a rich ratatui TUI belongs only inside `jackin console`; the CLI never spins one up.

* **CLI `--interactive`.** `jackin workspace claude-token setup <ws> --interactive` walks account → vault → item → field with **plain CLI prompts** (dialoguer Select/Input), offering `[ + New item ]` and `[ + New field ]` at the relevant steps. It does not open an alt-screen TUI. This keeps the CLI composable with a plain shell, scripts, and pipes, and means an operator at a terminal never has a second raw-mode UI fight their own.
* **Console wizard flow.** The rich, multi-step flow above belongs in `jackin console`, opened from the Auth tab — already on screen when the operator wants to configure a token — using the console's own modal stack so the operator never leaves the console and keeps all of its context (workspace state, auth-tab focus, in-progress edits). Every screen reuses a shared component: `source_picker`, `op_picker` in Create mode, and the `text_input` box for the naming sub-stages. (Scope is the auth form itself, so no `scope_picker`/`role_picker` step appears in the generate flow.)

The shared `op_picker` Create mode, the plain-prompt CLI path, and the console-side **generate-token action** are all shipped. On a Claude `oauth_token` auth form, `G` opens a source step (Plain text / 1Password); for 1Password it opens `op_picker` in Create mode. Pressing `G` stashes the open auth form into the same return-path slot the *provide* flow uses, so the dialog can be re-mounted when the mint finishes. On commit the `run_console` loop suspends the terminal (leaves raw-mode + alt-screen, mirroring `TerminalGuard`), runs the `claude setup-token` OAuth mint + the 1Password item create / read-back validation via `token_setup::mint_token_value` (which mints and validates but does **not** write jackin config), then resumes — the one place in the console that hands the real terminal to a child process. On success the loop re-mounts the Edit-auth dialog with the minted credential staged and focus on Save (via the same `apply_op_picker_to_auth_form` / `apply_plain_text_to_auth_form` helpers the provide path uses); the credential is persisted only when the operator Saves, exactly like picking an existing value. Cancel leaves the config unwired (any op item already created is harmless, matching the provide path). The scope follows the auth-form target: workspace level, a per-role override (open that role's form), or global (Settings). The plain-text target stores the minted token as a literal at that scope; 1Password stores an `op://` ref. The CLI path (`token_setup::run_setup`) keeps minting **and** persisting in one step because it has no form to return to. See [Workspace Claude Token Setup](/reference/roadmap/workspace-claude-token-setup/).

### Why reusability matters — the design principle behind this flow [#why-reusability-matters--the-design-principle-behind-this-flow]

The reason every screen above reuses an existing component is not code economy — it is **operator trust**. When the same meaning is always shown with the same shape, the operator learns the UI once and recognises it everywhere: a two-button dialog always means "pick one of two", a `+ ` row always means "add a new one", the bordered filter list always means "drill down", the input box always means "type a value here". A flow assembled from these known shapes is legible on first sight even though the operator has never seen this particular flow before. A flow that invents new shapes for the same meanings forces the operator to re-learn the UI at every screen and erodes that trust. Consistency in design is not polish; it is the contract that lets the operator predict what each screen does. This is why copy-pasting a component with slightly different look-and-feel is a bug, not a shortcut: it breaks the one-meaning-one-shape contract that makes the whole TUI predictable.
