feat(tui): workspace manager TUI (PR 3 of 3)#171
Merged
Conversation
Add secrets_masked / secrets_expanded / pending_env_key fields to EditorState, SecretsScopeTag type, EnvKey/EnvValue TextInputTarget variants, DeleteEnvVar ConfirmTarget variant. Extend change_count with env diff logic. Wire commit_editor_save to compute env diffs and call ConfigEditor::set_env_var / remove_env_var before save(). Extend build_confirm_save_lines with an "Env vars:" diff section. The new TextInputTarget / ConfirmTarget variants are unreachable in this commit; their handlers land in commit 2 (UI). Chose Option A: placeholder unreachable!() arm in apply_text_input_to_pending so all type definitions are established in commit 1. ConfirmTarget has no exhaustive-match site so it needed no placeholder (Modal::Confirm uses target: _). Removing Copy from TextInputTarget (EnvValue now carries a String) forced minor follow-on changes: pass-by-reference at the apply_text_input_to_pending callsite and `&Variant`-style comparisons in existing test assertions in prelude.rs and editor.rs. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Replace render_secrets_stub with a sectioned, hierarchical render: "Workspace env" header always visible, key rows with masked/unmasked display, collapsible per-agent override sections, "+ Add" sentinels, dirty markers. Wire input handlers: Enter (edit/expand/add per row kind), D (delete confirm), A (add), Ctrl+M (mask toggle), Left on expanded headers (collapse). Tab-cycle resets masking and collapses sections on leave. EnvKey/EnvValue handlers in handle_editor_modal route the two-step add flow; DeleteEnvVar handler removes the key from pending. Tab strip drops "Secrets ⏳" placeholder label and dim/italic style. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Add unit tests for change_count env-diff variants on EditorState and render-buffer tests for masking and section expand/collapse. Add seven integration scenarios in tests/manager_flow.rs covering edit, delete, add, masking toggle, section expand/collapse, dirty detection, and the two-step add flow. Append a "Workspace editor — Secrets tab" section to docs/src/content/docs/commands/console.mdx documenting the keybindings and scope semantics. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
The Secrets tab key-map advertises `→` as an alias for `Enter` on a collapsed agent override header (symmetric with `←` for collapse), and the docs landed in commit 3 say so. Commit 2 left this unwired — Right fell through to the tab-advance handler before the Secrets dispatch could see it. Add a guard at the top of the `Tab | Right` arm that, on the Secrets tab, expands the section when Right is pressed on a collapsed `AgentHeader`. Tab and Right-on-non-header still advance. Update the integration test to exercise `→` instead of `Enter` so a future regression of the guard is caught. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
…/item/field listing Add OpStructRunner trait with account_list, vault_list, item_list, item_get methods. Implement on OpCli using the same spawn-and-channel timeout pattern as OpRunner::read. Add serde shapes (RawOpVault, RawOpItem, RawOpItemDetail, RawOpField) that explicitly omit field values — the picker is a metadata browser that must not deserialize secret values into memory. Includes unit tests for JSON parsing, value-suppression, signed-out detection, and the concealed flag derivation. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
…lue input) Add OpPickerState, OpPickerStage, OpLoadState types in src/console/widgets/op_picker/. Add render function with three-pane drill-down, filter-as-you-type, Braille spinner, and four failure-state instructional displays. Wire Modal::OpPicker variant into modal_outer_rect, render_modal, and handle_editor_modal. Intercept Ctrl+O in the EnvValue TextInput arm of handle_editor_modal; restore original text on Esc-cancel. Add Ctrl+O footer hint to contextual_row_items Secrets key-row arm. Calls OpStructRunner from commit 5; field values are never deserialized. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Unit tests on the op picker state machine: filter narrowing, pane advancement clearing the filter, Esc back-navigation through Vault → Item → Field, concealed-first field sort, commit returning the op:// path. Integration tests in tests/manager_flow.rs: Ctrl+O from EnvValue modal opens OpPicker; Esc-cancel restores the EnvValue modal with original text intact. Docs: Ctrl+O row in Secrets key-map, plus a new "1Password picker" sub- section documenting requirements and failure-state messages. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
No Ctrl/Alt/Cmd modifiers in TUI keybindings; actions on rows rather than inside text modals. Applies project-wide starting with the Secrets tab cleanup that follows in subsequent commits. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Replace the Ctrl-modified mask binding with plain M, consistent with the new RULES.md TUI Keybindings rule. Update footer hints, docs, and tests. The ctrl_key test helper stays for now; it goes away in the next commit once the Ctrl+O picker redesign also no longer needs it. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
The TUI Keybindings rule now also forbids Shift modifiers, not just Ctrl/Alt/Cmd. Shift+Tab is a chord; rule is "single-key inputs only". Drop the standalone BackTab arm in the editor's tab-cycle handler (KeyCode::Left already implements the previous-tab fallthrough). Drop BackTab from confirm/save_discard/mount_dst_choice/confirm_save/git_prompt alternations — Left and h still cover prev-direction. Replace BackTab with Left in test drivers. Update inline comments. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Remove the Ctrl+O interception inside the EnvValue text modal. P on a Secrets-tab key row opens the picker directly; on commit, the chosen op:// reference is written straight to that key's pending value (no intermediate text modal). P on the +Add sentinel opens the picker first; the EnvKey modal then collects the key name with the path pre-stashed on EditorState. Drop saved_target/saved_label/original_value from OpPickerState — the picker no longer reconstructs the EnvValue modal. Update footer hints, docs, and tests; remove the ctrl_key test helper now that no caller remains. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Replace the full-screen ConsoleStage::Agent picker with a Modal::AgentPicker overlay on the manager list. Delete draw_agent_screen, the legacy handle_event Agent branch, ConsoleStage::Agent variant, ConsoleState's selected_agent / agent_query fields, and filtered_agents method. Three- branch launch logic: default_agent set → direct launch; single eligible agent → direct launch; multiple eligible → popup. Add AgentPickerState widget anchored on ManagerState.list_modal. New InputOutcome:: LaunchWithAgent variant. Tests updated: 5 legacy state/input tests deleted, 5 new manager_flow integration tests for the popup, 3 unit tests for the picker widget. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
`op account list --format json` against op CLI v2.x reports each account with an `account_uuid` key, not `id`. The picker probe was failing in the field with "missing field `id` at line 7 column 3" when an operator with multiple accounts pressed P → 1Password. Add a serde alias on RawOpAccount so either shape parses. Add a unit test using a real-shape JSON fixture. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Append a dated amendment block to the spec capturing how the Stage 3 implementation diverged from the 2026-04-23 design: manager-as-default landing (not an excursion), single-variant ConsoleStage with Modal::AgentPicker replacing the full-screen Agent stage, three-branch launch logic, the no-Ctrl/no-Shift keybinding philosophy, and the 1Password picker moving from a Ctrl+O modal-internal action to a P row-level action on the Secrets tab. Preserves the original record; amendments captured separately. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Add an Account pane as the first stage of the 1Password picker, visible only when the operator has 2+ accounts signed in to the op CLI. Single- account setups skip the pane entirely (no UX regression). All downstream op calls (vault_list, item_list, item_get) thread the chosen account_uuid via --account <id> so cross-account drilling works correctly. OpAccount gains email + url fields for human-readable display. RawOpAccount serde shape uses #[serde(default)] on optional fields for tolerance against older op versions. Esc from Vault returns to Account when multi-account; otherwise closes the picker as before. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
…indents The "Workspace env" section label was incorrectly focusable, so the cursor landed on it when the tab opened. The label is now rendered as a non-focusable section title above the list. All actionable rows (key rows, "+ Add" sentinel) render at the same indent column. Agent override sections still have focusable headers because they expand/collapse; their content rows align with workspace-level rows for visual consistency. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
…neral The General tab no longer shows the Default agent or Last used rows — both were read-only and added clutter. Default agent is now set on the Agents tab via the * key: cursor on an allowed agent and press * to set it as default, or press * on the current default to clear it. Disallowing the current default agent (Space) also clears the default. The default agent is marked visibly in the Agents-tab list. Footer hints updated. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Cache parsed Vec<OpAccount>/<OpVault>/<OpItem>/<OpField> results on ConsoleState so repeated picker drilling within one `jackin console` session is instant after the first call. Cache hits skip the op subprocess entirely. Manual refresh via `r` keypress in any picker pane re-fires the op call and updates the cache. The cache stores only structural metadata. Field values from `op item get` are never deserialized (RawOpField omits the JSON `value` key by design), so credentials never enter the cache. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Two picker UX fixes: 1. Scrolling now works in all four panes. The list widget's selection state tracks viewport offset; navigating below the visible window scrolls the list down. Repro: a vault with 20+ items. 2. Breadcrumbs drop the redundant trailing pane-name suffix. The pane content makes the type self-evident — "Items" / "Vaults" / "Fields" added noise. Titles now read "<email> → <vault>" instead of "<email> → <vault> → Items". Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
1Password lets multiple items share a title (two "Google" entries with different usernames, for example), and only the additional_information subtitle distinguishes them. Capture op item list's additional_information field on OpItem.subtitle, then render Item-pane rows as `<title> (<subtitle>)` with the parenthetical in PHOSPHOR_DIM so the title still reads as primary. The subtitle also participates in filter-as-you-type so operators can search by username when the titles collide. Items without an additional_information value (e.g., secure notes) render with just the title, no trailing parens. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
The Mounts and Agents tabs never rendered per-row dirty markers; the footer's `S save workspace (N changes)` is the canonical unsaved-state indicator. The General tab's Name/Working dir rows and the Secrets tab's key rows were the outliers — adding visual noise that made the editor inconsistent with itself. Drop the markers from both render paths and the now-unused dirty parameter from render_editor_row / render_secrets_key_line. Remove the orphaned env_key_is_dirty helper. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
… without keystrokes The console event loop was calling `event::read()` directly, which blocks until a key/mouse event arrives. While blocked, the Braille spinner stopped advancing and worker-thread results sat in the mpsc channel undelivered — both updates only ran when the next event unblocked the loop, making the picker feel frozen for seconds at a time even after `op` had finished. Switch to `event::poll(Duration::from_millis(50))` + conditional `event::read()`. Loop runs at 20 Hz regardless of input. Add `ManagerState::poll_picker_loads()` that drains the OpPicker channels on every tick, so loading state advances as soon as the worker finishes rather than waiting for keystroke pumping. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
… add Adding a new env var via Enter on the +Add sentinel now walks through: EnvKey modal (name) → SourcePicker modal (Plain text / 1Password) → EnvValue modal or OpPicker, depending on the choice. The 1Password button is disabled when the op CLI isn't on PATH; the picker still has the existing `P`-on-key-row shortcut for direct picker access. ConsoleState gains an `op_available` flag set once at startup. Sentinel- add invocations always go through the source picker; existing key-row edits and P-on-key-row picks are unchanged. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Caps Lock causes terminals to send letter keys with the SHIFT modifier set. Existing match arms guarded by `key.modifiers == KeyModifiers::NONE` silently rejected those, so operators with Caps Lock on saw `M`/`D`/`A`/ `P`/etc. no-op everywhere — D delete, A add, M mask/unmask, P 1Password, and so on. Replace the strict NONE check with a SHIFT-tolerant guard (`(modifiers - SHIFT).is_empty()`) on every letter shortcut in the editor, manager list, and widget handlers. Ctrl/Alt/Cmd modifiers are unchanged. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
…onment" wording Three small consistency fixes to the Secrets tab: 1. Console header reads "edit workspace · <name>" (Edit) and "create workspace" (Create) instead of "edit · <name>" / "new workspace" — the missing "workspace" suffix was ambiguous. 2. Empty Secrets tab no longer renders "Workspace env" label or "(no env vars)" placeholder — both add no information and aren't present on Mounts/Agents tabs. The empty state is now just the "+ Add environment variable" sentinel. 3. User-facing TUI text uses "environment" instead of "env" — the abbreviation makes sense for CLI subcommand names but reads as noise in the TUI. Sentinel text, modal titles, and footer hints updated. CLI verbs and Rust identifiers (EnvScope, set_env_var, WorkspaceConfig.env) are unchanged — the TUI is the only surface where the full word matters. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
…t stage is loading The picker title was emitting a full breadcrumb only after the load completed, so during the 1-3s op subprocess the modal said just "1Password" with no context — operator couldn't tell whether they were waiting for vaults, items, or fields. Stage transitions advance at request-time now (not result-time), so the title reflects the target of the in-flight load: `<email> → <vault>` while items load, `<email> → <vault> → <item>` while fields load, etc. Loading body messages adapt the same way: "loading vaults from <email>", "loading items from <vault>", "loading fields from <item>". Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
…creen Pressing Q anywhere outside the main manager list now opens an "Exit jackin'?" Y/N confirmation dialog. The operator can press N or Esc to return to exactly where they were — useful when Q is accidentally hit mid-flow. Q on the main screen still exits silently (today's behavior). Q in a text input modal (EnvKey, EnvValue, picker filter) is treated as input and never triggers the confirm. Routing is gated by is_on_main_screen + consumes_letter_input checks at the top of the event loop, so the dialog is the single chokepoint. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
…g body While loading an item's fields, the picker title now shows the parent context (`<email> → <vault>`) and the loading body names the item being descended into (`loading ChainArgos Redshift (donbeave)…`). Previously the title carried the item too, which made it ambiguous what was loading versus already loaded. Principle: title = where you are, body = what you're descending into. The same rule already applies to vault and item loads (which were unaffected by this change). Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
The EnvKey TextInput modal now carries the current scope's existing environment-variable keys as a "forbidden" list. While the operator types, an inline red warning shows when the name collides — and Enter is blocked until the name is changed or cleared. Workspace-scope and agent-scope sentinels each populate the forbidden list from their own env map, so a key named "DB_URL" can coexist on workspace and agent overrides without false positives. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…t, operator_env correction - Add op_picker/mod.rs to hot-spot table: 1712L/775L production (High); PR #171 AI-generated; OpPickerState types+behavior split opportunity; has 7-line //! doc; module map split into two rows (mod.rs + render.rs) - Add op_picker/render.rs 2-file split proposal: render.rs (coordinator) + render_pane.rs (pane/level renderers); no cross-dependency confirmed - Correct operator_env.rs total: 1569→2130L (880L production); update 4 occurrences across hot-spot table, ASCII tree, §4 analysis Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
… corrections - §4: Add "Alternative thesis: documentation-first verification" — challenges the two core assumptions behind file splitting (files-as-audit-unit and file-size-as-context-constraint); adds 7-criterion comparison table vs structure-first approach; introduces phased combined recommendation: Phase 1 = doc sprint (//! contracts + specs/ for 3 subsystems, 2-3 PRs, zero structural change); Phase 2 = splits only for >600L production files (reduces scope from 14+ to 4 files); Phase 3 = workspace if LOC > 150K - Fix stale LOC: app/mod.rs 951→979, config/editor.rs 1467→1548 (7 and 8 locations respectively; verified by fresh find|xargs wc -l scan) - §1 module map: add agent_picker.rs (436L), scope_picker.rs (201L), source_picker.rs (244L) — all PR #171 additions with //! docs Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
Resolves 9 conflicts to integrate PR #173 (link-check infrastructure), PR #171 (workspace manager TUI rewrite), and minor doc updates with the per-mount-isolation work. Conflict resolutions of note: - src/console/state.rs, src/console/preview.rs: take main's slimmer versions — main's #171 moved the agent-preview rendering into the new manager TUI components, where the iso badge work from this branch already lives (render/list.rs, render/editor.rs). - src/console/manager/state.rs: combine main's `DeleteEnvVar` + `SecretsScopeTag` with this branch's `DeleteIsolatedAndSave` / source-drift Confirm modal extension. - src/console/manager/input/editor.rs: route through main's `apply_editor_confirm` helper for simple variants while handling `DeleteIsolatedAndSave` inline (consumes `plan` and re-stashes as `EditorSaveFlow::PendingCommit`). - docs/.github/workflows/docs.yml: take main's verbatim — main has post-#173 refinements (lychee-action SHA pin, cache@v5 bump). - docs/.../per-mount-isolation.mdx: keep this branch's `<RepoFile>` conversion of `src/cli/cd.rs`. Drops one orphan test (`preselects_saved_workspace_for_nested_directory_under_mount_root`) that #171 removed from console/state.rs — the same coverage exists in workspace/resolve.rs:215 (`saved_workspace_match_depth_still_matches_nested_path_under_mount_root`). Adds `isolation: MountIsolation::Shared` to MountConfig literals that arrived via main's tests (state.rs, render/list.rs, manager_flow.rs ×6). All checks pass locally: - cargo fmt --check - cargo clippy -- -D warnings - cargo nextest run (1153/1153) - bun run check:repo-links (in docs/) - bun run build (in docs/) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…it, 4a/4c independence - Fix duplicate Rule 3 section introduced by previous edit; add docker.rs co-location note as third edge case (three edge cases, not two) - Add render/editor.rs as new Rule 5 violator: 1666L post-PR #171 (was listed as 782L); propose 6-file tab-by-tab split with auditability note on the security-adjacent Secrets tab - Add §10 execution-order note: 4a and 4c are independent — editor.rs imports AppConfig via crate::config re-exports regardless of 4a order - Append iteration 16 log entry with confidence table and weakest sections Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
… line count corrections - Add instance/auth.rs to //! priority queue at #4: four security invariants (0o600 perms, symlink rejection, TOCTOU-safe writes, macOS Keychain) documented in draft //! content - Add state.rs as new Rule 5 violator: 992L/628L production; 26+ types mixed with impl blocks; propose 5-file types/behavior split - Correct stale line counts: render/list.rs 1122→1989 (PR #171 added render_environments_subpanel); state.rs 865→992; priorities upgraded - Fix §7.9 snapshot function line refs: sentinel_description_pane 306→332, mounts_subpanel 408→433, render_tab_strip 180→269, test ref 720→944 - Renumber //! priority queue to 11 entries (was 10) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…split proposal - Close OQ2: agent_allow.rs read in full — 55L, correct //! doc, design sound; serves as model for //! priority queue pattern - Add render/list.rs as new Rule 5 violator: 668L production (PR #171 added render_environments_subpanel); propose 3-file split (mod.rs, details.rs, subpanels.rs); note import-path change for agents_block_agent_count - Update §1 module map: agent_allow.rs entry corrected with size/API Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…lit proposal - Correct input/editor.rs: 2349L total (was 1304L), 1141L production (was 547L) — PR #171 added Secrets-tab handlers; pub(super) fn handle_editor_modal at line 618 was invisible to previous grep pattern; now the largest production file in the codebase; priority → Critical - Correct input/save.rs: 1472L total, 661L production (was 567L) - Add 5-file split proposal for input/editor.rs: mod.rs (two dispatchers), secrets.rs (~500L AI-generated Secrets-tab), agents.rs, mounts.rs, general.rs - Update key insight paragraph naming input/editor.rs as largest production file Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…t/mod.rs corrected - Expand audit units table from 8 to 13 entries: add state/types.rs, state/editor.rs, input/editor/secrets.rs, render/list/subpanels.rs, input/save/preview.rs — all targeting PR #171 AI-generated console code - Add PR #171 context note linking 5 new entries to AI-generated code concern - Correct input/mod.rs module map: 369L, add InputOutcome enum to exports - Verify rust-toolchain.toml absence; §7.7 and §2 concept 25 already correct Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…able, EditorTab confirmed - Add §4 Rule 7 positive exemplars table: 7 files with //! docs graded 1-element (render/mod.rs), 2-element (input/save.rs etc), 3-element (env_model.rs, agent_allow.rs); PR #171 docs-discipline pattern noted - Correct render/mod.rs module map: 421L; FooterItem + palette constants + render_header + centered_rect_fixed added to key exports - Confirm EditorTab variants: General, Mounts, Agents, Secrets (Rust enum) vs "Secrets / Environments" (UI label); /stub qualifier already removed Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…MountConfig caveat - Correct too_many_lines count: 13 across 8 → 16 across 11 files (PR #171 added 5 suppressions in console/manager); add full breakdown table; update all 3 occurrences in roadmap - Fix FooterItem PR reference: #165 → #166 (confirmed by git log --follow) - Add MountConfig → MountSpec rename caveat to §7.5 snapshot test description Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…analyzed - Add console/mod.rs to hot-spot table: 406L/307L production (Low); correct module map from ~200 → 406L; note missing //! doc with ConsoleStage design block comment worth promoting - Add op_picker/render.rs to hot-spot table: 865L/545L production (Medium); PR #171 AI-generated; 14 functions in two logical groups (entry/helpers vs level renderers); split into levels.rs proposed - Correct 3 stale ~200L estimates for console/mod.rs across roadmap Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…t, operator_env correction - Add op_picker/mod.rs to hot-spot table: 1712L/775L production (High); PR #171 AI-generated; OpPickerState types+behavior split opportunity; has 7-line //! doc; module map split into two rows (mod.rs + render.rs) - Add op_picker/render.rs 2-file split proposal: render.rs (coordinator) + render_pane.rs (pane/level renderers); no cross-dependency confirmed - Correct operator_env.rs total: 1569→2130L (880L production); update 4 occurrences across hot-spot table, ASCII tree, §4 analysis Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
… corrections - §4: Add "Alternative thesis: documentation-first verification" — challenges the two core assumptions behind file splitting (files-as-audit-unit and file-size-as-context-constraint); adds 7-criterion comparison table vs structure-first approach; introduces phased combined recommendation: Phase 1 = doc sprint (//! contracts + specs/ for 3 subsystems, 2-3 PRs, zero structural change); Phase 2 = splits only for >600L production files (reduces scope from 14+ to 4 files); Phase 3 = workspace if LOC > 150K - Fix stale LOC: app/mod.rs 951→979, config/editor.rs 1467→1548 (7 and 8 locations respectively; verified by fresh find|xargs wc -l scan) - §1 module map: add agent_picker.rs (436L), scope_picker.rs (201L), source_picker.rs (244L) — all PR #171 additions with //! docs Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…it, 4a/4c independence - Fix duplicate Rule 3 section introduced by previous edit; add docker.rs co-location note as third edge case (three edge cases, not two) - Add render/editor.rs as new Rule 5 violator: 1666L post-PR #171 (was listed as 782L); propose 6-file tab-by-tab split with auditability note on the security-adjacent Secrets tab - Add §10 execution-order note: 4a and 4c are independent — editor.rs imports AppConfig via crate::config re-exports regardless of 4a order - Append iteration 16 log entry with confidence table and weakest sections Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
… line count corrections - Add instance/auth.rs to //! priority queue at #4: four security invariants (0o600 perms, symlink rejection, TOCTOU-safe writes, macOS Keychain) documented in draft //! content - Add state.rs as new Rule 5 violator: 992L/628L production; 26+ types mixed with impl blocks; propose 5-file types/behavior split - Correct stale line counts: render/list.rs 1122→1989 (PR #171 added render_environments_subpanel); state.rs 865→992; priorities upgraded - Fix §7.9 snapshot function line refs: sentinel_description_pane 306→332, mounts_subpanel 408→433, render_tab_strip 180→269, test ref 720→944 - Renumber //! priority queue to 11 entries (was 10) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…split proposal - Close OQ2: agent_allow.rs read in full — 55L, correct //! doc, design sound; serves as model for //! priority queue pattern - Add render/list.rs as new Rule 5 violator: 668L production (PR #171 added render_environments_subpanel); propose 3-file split (mod.rs, details.rs, subpanels.rs); note import-path change for agents_block_agent_count - Update §1 module map: agent_allow.rs entry corrected with size/API Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…lit proposal - Correct input/editor.rs: 2349L total (was 1304L), 1141L production (was 547L) — PR #171 added Secrets-tab handlers; pub(super) fn handle_editor_modal at line 618 was invisible to previous grep pattern; now the largest production file in the codebase; priority → Critical - Correct input/save.rs: 1472L total, 661L production (was 567L) - Add 5-file split proposal for input/editor.rs: mod.rs (two dispatchers), secrets.rs (~500L AI-generated Secrets-tab), agents.rs, mounts.rs, general.rs - Update key insight paragraph naming input/editor.rs as largest production file Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…t/mod.rs corrected - Expand audit units table from 8 to 13 entries: add state/types.rs, state/editor.rs, input/editor/secrets.rs, render/list/subpanels.rs, input/save/preview.rs — all targeting PR #171 AI-generated console code - Add PR #171 context note linking 5 new entries to AI-generated code concern - Correct input/mod.rs module map: 369L, add InputOutcome enum to exports - Verify rust-toolchain.toml absence; §7.7 and §2 concept 25 already correct Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…able, EditorTab confirmed - Add §4 Rule 7 positive exemplars table: 7 files with //! docs graded 1-element (render/mod.rs), 2-element (input/save.rs etc), 3-element (env_model.rs, agent_allow.rs); PR #171 docs-discipline pattern noted - Correct render/mod.rs module map: 421L; FooterItem + palette constants + render_header + centered_rect_fixed added to key exports - Confirm EditorTab variants: General, Mounts, Agents, Secrets (Rust enum) vs "Secrets / Environments" (UI label); /stub qualifier already removed Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…MountConfig caveat - Correct too_many_lines count: 13 across 8 → 16 across 11 files (PR #171 added 5 suppressions in console/manager); add full breakdown table; update all 3 occurrences in roadmap - Fix FooterItem PR reference: #165 → #166 (confirmed by git log --follow) - Add MountConfig → MountSpec rename caveat to §7.5 snapshot test description Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…analyzed - Add console/mod.rs to hot-spot table: 406L/307L production (Low); correct module map from ~200 → 406L; note missing //! doc with ConsoleStage design block comment worth promoting - Add op_picker/render.rs to hot-spot table: 865L/545L production (Medium); PR #171 AI-generated; 14 functions in two logical groups (entry/helpers vs level renderers); split into levels.rs proposed - Correct 3 stale ~200L estimates for console/mod.rs across roadmap Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…t, operator_env correction - Add op_picker/mod.rs to hot-spot table: 1712L/775L production (High); PR #171 AI-generated; OpPickerState types+behavior split opportunity; has 7-line //! doc; module map split into two rows (mod.rs + render.rs) - Add op_picker/render.rs 2-file split proposal: render.rs (coordinator) + render_pane.rs (pane/level renderers); no cross-dependency confirmed - Correct operator_env.rs total: 1569→2130L (880L production); update 4 occurrences across hot-spot table, ASCII tree, §4 analysis Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
… corrections - §4: Add "Alternative thesis: documentation-first verification" — challenges the two core assumptions behind file splitting (files-as-audit-unit and file-size-as-context-constraint); adds 7-criterion comparison table vs structure-first approach; introduces phased combined recommendation: Phase 1 = doc sprint (//! contracts + specs/ for 3 subsystems, 2-3 PRs, zero structural change); Phase 2 = splits only for >600L production files (reduces scope from 14+ to 4 files); Phase 3 = workspace if LOC > 150K - Fix stale LOC: app/mod.rs 951→979, config/editor.rs 1467→1548 (7 and 8 locations respectively; verified by fresh find|xargs wc -l scan) - §1 module map: add agent_picker.rs (436L), scope_picker.rs (201L), source_picker.rs (244L) — all PR #171 additions with //! docs Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
donbeave
added a commit
that referenced
this pull request
Apr 26, 2026
…183) * docs(roadmap): iteration 13 — AI code verifiability framing, config/types.rs full spec Primary goal shift: codebase must be verifiable for AI-generated code. - §0: replace generic description with explicit verifiability rationale (module contracts, localised concerns, types/behaviour separation) - §4 intro: add "Why structure matters for AI-generated code" section with audit-units table mapping each post-split file to one reviewable question - §4 4a: expand config/types.rs from description to full execution spec — exact type list, post-split mod.rs content, zero-change submodule guarantee (verified: agents.rs/persist.rs/workspaces.rs use super::T which resolves through mod.rs re-exports unchanged), impl-extension pattern already in use documented Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 14 — editor method map, app helpers, //! queue - §4 4c: config/editor.rs split is now execution-ready — complete 6-file method-to-file table with private helper placement verified (validate_candidate→io.rs, table_path_mut→mod.rs pub(super), auth_forward_str→agent_ops.rs, create_workspace delegates to AppConfig) - §4 4e: app/mod.rs split complete — all private helpers mapped (parse_auth_forward_mode_from_cli→config_cmd.rs, workspace_env_scope→workspace_cmd.rs, print_env_table note, remove_data_dir_if_exists→dispatch.rs) - §10 step 5: add //! priority queue — 10 files with draft content, prioritised by cold-landing impact and AI audit risk; selector.rs and instance/mod.rs explicitly document the /→__ invariant Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 15 — dep graph fix, trust.rs safety, OQ1 closed - §4 4d: correct operator_env dependency graph — layers.rs imports both mod.rs (OpRunner) AND client.rs (OpCli for non-injectable resolve_operator_env wrapper at line 797); still a valid DAG - §4 4f: verify trust.rs split safety — FnOnce injection pattern means launch_pipeline.rs has zero dependency on trust.rs; import chain documented; trust bypass audit now requires reading only ~60L - §9 OQ1 closed: op_cache.rs read in full — 4-level structure, per-level invalidation, no TTL/expiry (expiry handled at OpCli subprocess level), DEFAULT_ACCOUNT_KEY sentinel documented Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 16 — CommandRunner Rule 3, render/editor split, 4a/4c independence - Fix duplicate Rule 3 section introduced by previous edit; add docker.rs co-location note as third edge case (three edge cases, not two) - Add render/editor.rs as new Rule 5 violator: 1666L post-PR #171 (was listed as 782L); propose 6-file tab-by-tab split with auditability note on the security-adjacent Secrets tab - Add §10 execution-order note: 4a and 4c are independent — editor.rs imports AppConfig via crate::config re-exports regardless of 4a order - Append iteration 16 log entry with confidence table and weakest sections Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 17 — instance/auth.rs audit, state.rs split, line count corrections - Add instance/auth.rs to //! priority queue at #4: four security invariants (0o600 perms, symlink rejection, TOCTOU-safe writes, macOS Keychain) documented in draft //! content - Add state.rs as new Rule 5 violator: 992L/628L production; 26+ types mixed with impl blocks; propose 5-file types/behavior split - Correct stale line counts: render/list.rs 1122→1989 (PR #171 added render_environments_subpanel); state.rs 865→992; priorities upgraded - Fix §7.9 snapshot function line refs: sentinel_description_pane 306→332, mounts_subpanel 408→433, render_tab_strip 180→269, test ref 720→944 - Renumber //! priority queue to 11 entries (was 10) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 18 — agent_allow OQ2 closed, render/list.rs split proposal - Close OQ2: agent_allow.rs read in full — 55L, correct //! doc, design sound; serves as model for //! priority queue pattern - Add render/list.rs as new Rule 5 violator: 668L production (PR #171 added render_environments_subpanel); propose 3-file split (mod.rs, details.rs, subpanels.rs); note import-path change for agents_block_agent_count - Update §1 module map: agent_allow.rs entry corrected with size/API Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 19 — input/editor.rs critical correction, split proposal - Correct input/editor.rs: 2349L total (was 1304L), 1141L production (was 547L) — PR #171 added Secrets-tab handlers; pub(super) fn handle_editor_modal at line 618 was invisible to previous grep pattern; now the largest production file in the codebase; priority → Critical - Correct input/save.rs: 1472L total, 661L production (was 567L) - Add 5-file split proposal for input/editor.rs: mod.rs (two dispatchers), secrets.rs (~500L AI-generated Secrets-tab), agents.rs, mounts.rs, general.rs - Update key insight paragraph naming input/editor.rs as largest production file Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 20 — console splits in §10, MSRV evidence, animation.rs verdict - Add console/manager/ as §10 Step 4f group with 5 sub-steps in priority order; rename existing 4f (launch.rs) → 4g; add circular-import risk note for ManagerStage/EditorState split sequencing - Analyze tui/animation.rs: 582L all-production, no split needed (banner_grid is a tightly-coupled rendering loop); section comments compensate for missing //! - Partially close OQ3: u64::is_multiple_of (stabilized 1.86) found in animation.rs; within declared MSRV 1.94; full cargo +1.94.0 check deferred (toolchain unavailable) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 21 — input/save.rs split, //! queue fix, save.rs corrections - Add input/save.rs split proposal: 4 pub(super) fns discovered; 3-file split (mod.rs + flow.rs + preview.rs); no cross-dependency between flow and preview groups; §10 4f-v updated from Optional to concrete plan - Fix //! queue preamble: "first 10 files" → "first 11 files" - Correct save.rs module map (1418→1472L, correct key exports) and hot-spot table note (begin_editor_save ~280L → ~118L; commit_editor_save is the Phase 2 partner at ~149L) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 22 — input/list.rs and mount_info.rs analysis - Analyze input/list.rs: 214L production (tests at 215); has //! doc; two focused pub(super) fns; no split needed; Low priority; correct module map - Add mount_info.rs to hot-spot table: 277L production; Low priority; has //! doc; correct module map with 3 public enums + inspect fn - Fix stale §2 diagnosis note: docs/internal/roadmap/ now exists Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 23 — audit units table +5 console rows, input/mod.rs corrected - Expand audit units table from 8 to 13 entries: add state/types.rs, state/editor.rs, input/editor/secrets.rs, render/list/subpanels.rs, input/save/preview.rs — all targeting PR #171 AI-generated console code - Add PR #171 context note linking 5 new entries to AI-generated code concern - Correct input/mod.rs module map: 369L, add InputOutcome enum to exports - Verify rust-toolchain.toml absence; §7.7 and §2 concept 25 already correct Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 24 — render/mod.rs analysis, //! exemplars table, EditorTab confirmed - Add §4 Rule 7 positive exemplars table: 7 files with //! docs graded 1-element (render/mod.rs), 2-element (input/save.rs etc), 3-element (env_model.rs, agent_allow.rs); PR #171 docs-discipline pattern noted - Correct render/mod.rs module map: 421L; FooterItem + palette constants + render_header + centered_rect_fixed added to key exports - Confirm EditorTab variants: General, Mounts, Agents, Secrets (Rust enum) vs "Secrets / Environments" (UI label); /stub qualifier already removed Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 25 — too_many_lines recount, FooterItem PR, MountConfig caveat - Correct too_many_lines count: 13 across 8 → 16 across 11 files (PR #171 added 5 suppressions in console/manager); add full breakdown table; update all 3 occurrences in roadmap - Fix FooterItem PR reference: #165 → #166 (confirmed by git log --follow) - Add MountConfig → MountSpec rename caveat to §7.5 snapshot test description Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 26 — console/mod.rs and op_picker/render.rs analyzed - Add console/mod.rs to hot-spot table: 406L/307L production (Low); correct module map from ~200 → 406L; note missing //! doc with ConsoleStage design block comment worth promoting - Add op_picker/render.rs to hot-spot table: 865L/545L production (Medium); PR #171 AI-generated; 14 functions in two logical groups (entry/helpers vs level renderers); split into levels.rs proposed - Correct 3 stale ~200L estimates for console/mod.rs across roadmap Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 27 — op_picker/mod.rs discovery, render split, operator_env correction - Add op_picker/mod.rs to hot-spot table: 1712L/775L production (High); PR #171 AI-generated; OpPickerState types+behavior split opportunity; has 7-line //! doc; module map split into two rows (mod.rs + render.rs) - Add op_picker/render.rs 2-file split proposal: render.rs (coordinator) + render_pane.rs (pane/level renderers); no cross-dependency confirmed - Correct operator_env.rs total: 1569→2130L (880L production); update 4 occurrences across hot-spot table, ASCII tree, §4 analysis Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 28 — op_picker/mod.rs 3-file split, count corrections - Add op_picker/mod.rs formal 3-file split: loading.rs (async load family ~120L) + keys.rs (4 level key handlers ~315L) + mod.rs (types/constructors) - Correct "24 files" → "28+" for 500L threshold count - Update total LOC: ~40,664 → ~43,587 (2 occurrences, with provenance note) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 29 — op_picker execution order + file_browser analysis - §10 Step 4f: expand from 5 to 7 sub-steps; add 4f-vi (op_picker/mod.rs → mod.rs + loading.rs + keys.rs) and 4f-vii (op_picker/render.rs → render.rs + pane.rs); document impl-extension and import-path caveats - §4 //! exemplars: add file_browser/ subsystem analysis — all 5 files have //! docs, no file exceeds ~350L production; classified as exemplar (not a split candidate); document git_prompt.rs coupling-density justification and input.rs as 28-file false positive (144L production) - §1 module map: expand single file_browser/ row to 5 individual rows with production LOC and dominant concern per file Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 30 — challenge split-first thesis, fresh LOC corrections - §4: Add "Alternative thesis: documentation-first verification" — challenges the two core assumptions behind file splitting (files-as-audit-unit and file-size-as-context-constraint); adds 7-criterion comparison table vs structure-first approach; introduces phased combined recommendation: Phase 1 = doc sprint (//! contracts + specs/ for 3 subsystems, 2-3 PRs, zero structural change); Phase 2 = splits only for >600L production files (reduces scope from 14+ to 4 files); Phase 3 = workspace if LOC > 150K - Fix stale LOC: app/mod.rs 951→979, config/editor.rs 1467→1548 (7 and 8 locations respectively; verified by fresh find|xargs wc -l scan) - §1 module map: add agent_picker.rs (436L), scope_picker.rs (201L), source_picker.rs (244L) — all PR #171 additions with //! docs Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 31 — fix 600L→800L threshold error, correct LOC - §4 alternative thesis: correct ">600L production → 4 files" claim introduced in iteration 30; re-verified all 9 candidate files via #[cfg(test)] line position; threshold must be >800L to get exactly 4 files (9 exceed 600L); add verification table with test-start lines - Production LOC corrections (5+ locations each): launch.rs 1085→~1077, operator_env.rs 810→~880, app/mod.rs 928→~957, config/editor.rs 503→~584 - §2 OpPicker row: replace vague "no entry yet" with confirmed gap: PROJECT_STRUCTURE.md line 53 still lists pre-PR#171 widget set (10 named); omits op_picker/, agent_picker.rs, scope_picker.rs, source_picker.rs and pre-dates the manager/ sub-structure split Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 32 — two-tier spec arch, behavioral spec template - §8.1: Add two-tier spec architecture table distinguishing feature specs (public Starlight MDX, user-facing) from behavioral specs (internal docs/internal/specs/, for AI code verification) — resolves contradiction between §4 (which said docs/internal/specs/) and §8.1 (which said "no longer needed; specs are public") - §8.1: Add concrete behavioral spec template for op_picker/ with state machine table and 3 INV invariant entries each with a grep-executable "Verify by:" command; template directly usable for the 3 Phase 1 specs - §8.1: Remove erroneous "docs/internal/specs/ no longer needed" claim - Confirmed render/editor.rs ~736L and render/list.rs ~668L production (no interspersed production code — all test blocks follow consecutively) Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 33 — executive summary, §0 correctness - §0: Add executive summary (~300 words) with core problem, 3-phase recommendation, key counter-argument, and navigation table pointing to §2/§4/§7/§8/§10 by question — resolves the meta-irony of a readability roadmap with no entry-point orientation - §0 item 2: "1569-line monolith" → "2130-line monolith" (operator_env.rs current verified size; stale reference was in the first section readers see) - §0 item 3: Add "(selective)" qualifier and explicit note that standard Rust co-locates struct+impl — impl-extension pattern is justified only for files >800L production, not as a universal rule Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 34 — spec priority reorder, §10 Phase 1 track - §0 + §4 Phase 1: Prioritize runtime/launch.rs behavioral spec (no //! doc, ~1077L production, critical path — all jackin load failures trace here); drop config/editor.rs from Phase 1 (its 963L test suite already serves as behavioral spec — tests are behavioral examples); reduce Phase 1 from 3 specs to 2 specs; add reasoning for the priority ordering - §10 Step 2: Split into two parallel tracks — Track A (cc-sdd tooling setup) + Track B (Phase 1 behavioral spec authoring); Track B includes specific INV invariants to capture for runtime/launch.rs grounded in reading the actual function structure (step comment positions); adds sequencing rationale: spec must precede structural splits Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 35 — verified INV entries for runtime/launch.rs Read load_agent_with lines 553-892 in full. Replaced 3 draft INVs from iteration 34 (inferred from step comment positions) with 5 verified INVs citing exact line numbers: - INV-1: trust gate (line 594) precedes image build (line 736) - INV-2: container name claimed (line 754) between image build and network - INV-3: token verified (line 763) before network creation (line 827) - INV-4: render_exit called at lines 886 AND 890 (all exit paths) - INV-5: cleanup disarm semantics — Running→disarm, clean exit→cleanup, crash→disarm (explains jackin hardline compatibility) Corrected wrong line number: claim_container_name call is at 754, not 918 (918 is the function definition). Each INV has a grep-executable Verify by. Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 36 — CI gate for PROJECT_STRUCTURE.md freshness §3: Add "Preventing future PROJECT_STRUCTURE.md staleness" subsection with three concrete options: - Option A: CONTRIBUTING.md rule (necessary but insufficient) - Option B: ci.yml git-diff-scoped shell check (recommended) — only checks files added in the current PR so it doesn't require fixing existing stale entries before merging; greps for module directory name in prose - Option C: Structured TOML module registry (over-engineered for scale) Includes concrete YAML snippet for Option B grounded in the check:repo-links.ts pattern already established in docs/scripts/ Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iterations 36-37 — CI gate + greenfield workspace architecture Iteration 36: - §3: Add "Preventing future PROJECT_STRUCTURE.md staleness" subsection with 3 options (CONTRIBUTING.md rule / ci.yml git-diff check / TOML registry); recommend Option B (git-diff-scoped YAML step) with concrete snippet grounded in existing check:repo-links.ts pattern from docs/scripts/ Iteration 37 (operator directive: greenfield Rust structure): - §4: Add "Greenfield architecture — ideal structure for a growing project" section based on verified cross-module dependency graph (grep iteration 37) - Confirms dependency tiers: workspace/manifest/docker/paths/selector = Tier 0; config/tui/instance = Tier 1; operator_env/runtime/repo = Tier 2; console = Tier 3 - Key finding: workspace/ is LOWER-level than config/ (config re-exports workspace types at lines 5-6); ideal naming inverted in greenfield (jackin-core > jackin-config) - Documents ideal 6-crate workspace: jackin-core, jackin-config, jackin-tui, jackin-runtime, jackin-console, jackin-shell + thin binary - Notes console/ has NO runtime/ import — cleanest pre-existing crate boundary - Bridge: incremental splits (4a, 4d, 4g) are pre-work toward workspace migration Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 38 — Rust workspace standards, community evidence Ground workspace recommendation in real-world project research: - ripgrep (9 crates), gitui (5 crates) went workspace due to library consumers - starship and fd-find stay single-crate at 1M+ LOC — no library use case - jackin (43K LOC, no external consumers) maps to starship/fd pattern → single-crate is community-standard; "stay single-crate" recommendation confirmed Update greenfield workspace structure to follow matklad's pattern: - Virtual manifest at root (no [package] in root Cargo.toml) - Flat crates/ directory (not nested); crate names match folder names - version = "0.0.0" for unpublished internal crates - Add inline dep comments to each crate in the ASCII structure Add research notes: ripgrep/starship/gitui/fd-find Cargo.toml findings + Cargo workspaces reference + matklad "Large Rust Workspaces" (2021-08-22) Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): revise §7.9 + §3 — adopt per-directory README.md §7.9: Reverse previous "reject" recommendation to "adopt" per-directory README.md for major src/ module directories. Rationale: README.md is AI-native — Claude Code, Copilot, Cursor load it automatically on directory entry, giving AI agents orientation before they decide which file to open. PROJECT_STRUCTURE.md being confirmed stale removes the main argument for the "single root file" approach. Add three-layer documentation model table: - README.md: directory orientation (AI + human, on entry) - AGENTS.md: agent workflow rules (root, session start) - CLAUDE.md: @AGENTS.md pointer only — NEVER add content here - //! docs: file-level contracts (when reading/editing) Add specific README.md content targets for 7 directories (src/, src/runtime/, src/console/, src/console/manager/, src/console/widgets/, docs/, docs/internal/). §3 target document shape: Add per-directory README.md to proposed hierarchy; add docs/internal/specs/ explicitly; note CLAUDE.md design principle (single-line @AGENTS.md — never duplicate content). Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): internal docs are browsable — unified Starlight site Operator directive: internal docs (architecture, specs, ADRs, roadmap) should be browsable, not hidden filesystem files. They are a different TYPE of docs focused on implementation details and vision, published as a "Developer Reference" section of the Starlight site. §3 target document shape: - docs/internal/ moves into docs/src/content/docs/internal/ (Starlight pages) - Browsable at jackin.tailrocks.com/internal/ - Sidebar: "Developer Reference" group (collapsed by default) with sub-sections for architecture, code-tour, contributing, testing, decisions, specs, roadmap - Include astro.config.ts sidebar config snippet §8.1 two-tier spec distinction eliminated: - Feature specs and behavioral specs both live at docs/src/content/docs/internal/specs/ - Type expressed via spec_type: behavioral | feature frontmatter, not filesystem location - Both browsable and searchable via Starlight; AI agents can be pointed to URLs §8.3 + §4: - All docs/internal/specs/ paths → docs/src/content/docs/internal/specs/ - ADRs: docs/internal/decisions/ → docs/src/content/docs/internal/decisions/ (browsable) - README.md pointer for src/runtime/ updated to URL reference Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): §11 — modern Rust docs platform (future project) Add §11 capturing the vision for a modern docs.rs alternative with: - rustdoc JSON ingestion → Astro Starlight presentation - MCP server for AI agent queries (Context7 alternative for Rust) - Rust-specific query types: rust_get_context(), rust_find_impls(), rust_search_types() — things Context7 cannot provide - Comparison table vs Context7 - Architecture diagram (ingestion → processing → Starlight + MCP) - Name candidates: rustlight, ferrodoc, cargo-starlight / starlight.rs - Note that jackin's §7.15 gen-rust-api.ts pipeline is the intentional prototype for the platform's processing and presentation layers Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 39 — update §0, fix stale internal/ paths §0 executive summary: rewrite to reflect decisions from iterations 30-38: - browsable internal docs (jackin.tailrocks.com/internal/) - per-directory README.md adoption (§7.9 reversed) - CLAUDE.md = @AGENTS.md single-line pointer only - greenfield workspace architecture (matklad's virtual manifest pattern) - §11 future project: modern Rust docs platform / Context7-for-Rust - document size 1800+ → 2200+ Fix stale docs/internal/ bare paths not caught by iteration 38 sweep: - Mermaid diagram: INTERNAL_ROADMAP, INTERNAL_CODE_TOUR → Starlight paths - §7.10 ADRs: docs/internal/decisions/NNN-title.md → .mdx Starlight path - §10 Track B item 2: op-picker spec path → Starlight MDX Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 40 — §7.15 pipeline + Rule 4 pub audit §7.15 (new): rustdoc JSON → Astro Starlight API documentation pipeline - Three options: rustdoc HTML publish / rustdoc JSON + bun script (recommended) / rustdoc-json crate as Rust binary - Option B recommended: matches existing docs/scripts/ pattern, nightly isolated to separate CI step, zero effect on stable build - Key design: URL at /internal/api/, cross-links to behavioral specs, Starlight unified search, prototype for §11 future project - Pub(crate) note: gen-rust-api.ts can feed Rule 4 visibility audit - Recommend: adopt after Phase 1 //! sprint (value ∝ coverage) §4 Rule 4 pub discipline: replace estimated "50-100 items" guess with verified numbers from iteration 40 grep: - 257 bare pub items, 21 pub(crate), 61 pub(super) across 94 files - 0 uses of unreachable_pub lint — no enforcement gate - Top violators: operator_env.rs (17), tui/output.rs (13), planner.rs (8) - Add concrete Cargo.toml [lints.rust] snippet: unreachable_pub = "warn" - Revised scope: ~150-200 mechanical conversions (excludes entry points) Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): split research into 19 actionable items Delete _research_notes.md (no longer needed). Replace 2343L READABILITY_AND_MODERNIZATION.md with: - README.md: index of all 19 items with phase, ordering notes, links - READABILITY_AND_MODERNIZATION.md: lightweight research summary (63L) - items/ITEM-001 through ITEM-019: individual actionable items Items by phase: Phase 1 (low risk, no confirmation): ITEM-001..004, 006..011 Phase 1 (needs confirmation): ITEM-005, 016, 018 Phase 2 (structural splits, confirmation required): ITEM-012..015 Phase 3 (deferred): ITEM-017 (rustdoc pipeline), ITEM-019 (workspace) Each item has: summary, key files with line numbers, steps, what needs confirmation, and relevant research backing from the 40-iteration analysis loop. Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): migrate 19 items to Starlight reference/roadmap section Move all codebase health roadmap items from docs/internal/roadmap/items/ (plain Markdown, not browsable) to docs/src/content/docs/reference/roadmap/ (MDX pages, browsable at jackin.tailrocks.com/reference/roadmap/). Adds a new "Codebase health" sidebar group (Phase 1 → Phase 3) to astro.config.ts. Deletes the old items/ directory. Updates the internal README to redirect to the new location. Also adds codebase-readability.mdx — a new overview item that captures the overall readability/restructuring program with a recommended execution order: file splits first, then greenfield workspace, then per-directory README+AGENTS.md, then docs and specs. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): remove premature internal/roadmap/README.md The internal/ structure doesn't exist yet — it will be created as part of the roadmap items themselves. No need for a redirect stub now. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): remove READABILITY_AND_MODERNIZATION.md research archive All content has been distilled into the individual Starlight roadmap pages. The full 2343L research is preserved in git history at commit b7e9fc2. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): fix check:repo-links errors + remove iteration log - Replace plain code spans with <RepoFile> for validate.rs, mise.toml, Cargo.toml, and op_picker/mod.rs - Remove deleted READABILITY_AND_MODERNIZATION.md reference from codebase-readability.mdx - Delete _iteration_log.md (git history is the archive) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): fix lychee false-positive link in move-contributing-testing The example redirect text contained a markdown hyperlink to a proposed future file path that doesn't exist yet. Changed to a code span. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> --------- Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Co-authored-by: Claude <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
May 6, 2026
* feat(tui): extend EditorState and save path for Secrets tab
Add secrets_masked / secrets_expanded / pending_env_key fields to
EditorState, SecretsScopeTag type, EnvKey/EnvValue TextInputTarget variants,
DeleteEnvVar ConfirmTarget variant. Extend change_count with env diff
logic. Wire commit_editor_save to compute env diffs and call
ConfigEditor::set_env_var / remove_env_var before save(). Extend
build_confirm_save_lines with an "Env vars:" diff section.
The new TextInputTarget / ConfirmTarget variants are unreachable in this
commit; their handlers land in commit 2 (UI). Chose Option A: placeholder
unreachable!() arm in apply_text_input_to_pending so all type definitions
are established in commit 1. ConfirmTarget has no exhaustive-match site
so it needed no placeholder (Modal::Confirm uses target: _).
Removing Copy from TextInputTarget (EnvValue now carries a String) forced
minor follow-on changes: pass-by-reference at the apply_text_input_to_pending
callsite and `&Variant`-style comparisons in existing test assertions in
prelude.rs and editor.rs.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): implement Secrets tab render and input handlers
Replace render_secrets_stub with a sectioned, hierarchical render:
"Workspace env" header always visible, key rows with masked/unmasked
display, collapsible per-agent override sections, "+ Add" sentinels, dirty
markers. Wire input handlers: Enter (edit/expand/add per row kind), D
(delete confirm), A (add), Ctrl+M (mask toggle), Left on expanded headers
(collapse). Tab-cycle resets masking and collapses sections on leave.
EnvKey/EnvValue handlers in handle_editor_modal route the two-step add
flow; DeleteEnvVar handler removes the key from pending. Tab strip drops
"Secrets ⏳" placeholder label and dim/italic style.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* test(tui): Secrets tab unit and integration tests; update console docs
Add unit tests for change_count env-diff variants on EditorState and
render-buffer tests for masking and section expand/collapse. Add seven
integration scenarios in tests/manager_flow.rs covering edit, delete,
add, masking toggle, section expand/collapse, dirty detection, and the
two-step add flow. Append a "Workspace editor — Secrets tab" section
to docs/src/content/docs/commands/console.mdx documenting the keybindings
and scope semantics.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): wire Right arrow to expand collapsed Secrets sections
The Secrets tab key-map advertises `→` as an alias for `Enter` on a
collapsed agent override header (symmetric with `←` for collapse), and
the docs landed in commit 3 say so. Commit 2 left this unwired — Right
fell through to the tab-advance handler before the Secrets dispatch
could see it. Add a guard at the top of the `Tab | Right` arm that, on
the Secrets tab, expands the section when Right is pressed on a
collapsed `AgentHeader`. Tab and Right-on-non-header still advance.
Update the integration test to exercise `→` instead of `Enter` so a
future regression of the guard is caught.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(op): add OpStructRunner trait and OpCli implementation for vault/item/field listing
Add OpStructRunner trait with account_list, vault_list, item_list, item_get
methods. Implement on OpCli using the same spawn-and-channel timeout pattern
as OpRunner::read. Add serde shapes (RawOpVault, RawOpItem, RawOpItemDetail,
RawOpField) that explicitly omit field values — the picker is a metadata
browser that must not deserialize secret values into memory. Includes unit
tests for JSON parsing, value-suppression, signed-out detection, and the
concealed flag derivation.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): 1Password vault/item/field picker modal (Ctrl+O from EnvValue input)
Add OpPickerState, OpPickerStage, OpLoadState types in
src/console/widgets/op_picker/. Add render function with three-pane
drill-down, filter-as-you-type, Braille spinner, and four failure-state
instructional displays. Wire Modal::OpPicker variant into modal_outer_rect,
render_modal, and handle_editor_modal. Intercept Ctrl+O in the EnvValue
TextInput arm of handle_editor_modal; restore original text on Esc-cancel.
Add Ctrl+O footer hint to contextual_row_items Secrets key-row arm. Calls
OpStructRunner from commit 5; field values are never deserialized.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* test(tui): op picker unit/integration tests; update console docs
Unit tests on the op picker state machine: filter narrowing, pane
advancement clearing the filter, Esc back-navigation through Vault → Item →
Field, concealed-first field sort, commit returning the op:// path.
Integration tests in tests/manager_flow.rs: Ctrl+O from EnvValue modal opens
OpPicker; Esc-cancel restores the EnvValue modal with original text intact.
Docs: Ctrl+O row in Secrets key-map, plus a new "1Password picker" sub-
section documenting requirements and failure-state messages.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* chore(rules): add TUI Keybindings section to RULES.md
No Ctrl/Alt/Cmd modifiers in TUI keybindings; actions on rows rather than
inside text modals. Applies project-wide starting with the Secrets tab
cleanup that follows in subsequent commits.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(tui): Ctrl+M → M for Secrets-tab mask toggle
Replace the Ctrl-modified mask binding with plain M, consistent with the
new RULES.md TUI Keybindings rule. Update footer hints, docs, and tests.
The ctrl_key test helper stays for now; it goes away in the next commit
once the Ctrl+O picker redesign also no longer needs it.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(tui): drop Shift+Tab support; tighten RULES.md rule
The TUI Keybindings rule now also forbids Shift modifiers, not just
Ctrl/Alt/Cmd. Shift+Tab is a chord; rule is "single-key inputs only".
Drop the standalone BackTab arm in the editor's tab-cycle handler
(KeyCode::Left already implements the previous-tab fallthrough). Drop
BackTab from confirm/save_discard/mount_dst_choice/confirm_save/git_prompt
alternations — Left and h still cover prev-direction. Replace BackTab
with Left in test drivers. Update inline comments.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(tui): move 1Password picker to row-level P action
Remove the Ctrl+O interception inside the EnvValue text modal. P on a
Secrets-tab key row opens the picker directly; on commit, the chosen
op:// reference is written straight to that key's pending value (no
intermediate text modal). P on the +Add sentinel opens the picker first;
the EnvKey modal then collects the key name with the path pre-stashed
on EditorState. Drop saved_target/saved_label/original_value from
OpPickerState — the picker no longer reconstructs the EnvValue modal.
Update footer hints, docs, and tests; remove the ctrl_key test helper
now that no caller remains.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): Modal::AgentPicker — inline agent disambiguation popup
Replace the full-screen ConsoleStage::Agent picker with a Modal::AgentPicker
overlay on the manager list. Delete draw_agent_screen, the legacy
handle_event Agent branch, ConsoleStage::Agent variant, ConsoleState's
selected_agent / agent_query fields, and filtered_agents method. Three-
branch launch logic: default_agent set → direct launch; single eligible
agent → direct launch; multiple eligible → popup. Add AgentPickerState
widget anchored on ManagerState.list_modal. New InputOutcome::
LaunchWithAgent variant. Tests updated: 5 legacy state/input tests
deleted, 5 new manager_flow integration tests for the popup, 3 unit
tests for the picker widget.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(op): accept account_uuid alongside id in `op account list` output
`op account list --format json` against op CLI v2.x reports each account
with an `account_uuid` key, not `id`. The picker probe was failing in the
field with "missing field `id` at line 7 column 3" when an operator with
multiple accounts pressed P → 1Password. Add a serde alias on RawOpAccount
so either shape parses. Add a unit test using a real-shape JSON fixture.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(spec): amend workspace-manager-tui-design for 2026-04-25 changes
Append a dated amendment block to the spec capturing how the Stage 3
implementation diverged from the 2026-04-23 design: manager-as-default
landing (not an excursion), single-variant ConsoleStage with
Modal::AgentPicker replacing the full-screen Agent stage, three-branch
launch logic, the no-Ctrl/no-Shift keybinding philosophy, and the
1Password picker moving from a Ctrl+O modal-internal action to a P
row-level action on the Secrets tab. Preserves the original record;
amendments captured separately.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(op): multi-account 1Password picker
Add an Account pane as the first stage of the 1Password picker, visible
only when the operator has 2+ accounts signed in to the op CLI. Single-
account setups skip the pane entirely (no UX regression). All downstream
op calls (vault_list, item_list, item_get) thread the chosen account_uuid
via --account <id> so cross-account drilling works correctly.
OpAccount gains email + url fields for human-readable display. RawOpAccount
serde shape uses #[serde(default)] on optional fields for tolerance against
older op versions. Esc from Vault returns to Account when multi-account;
otherwise closes the picker as before.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): Secrets tab — skip Workspace label as focusable row, align indents
The "Workspace env" section label was incorrectly focusable, so the
cursor landed on it when the tab opened. The label is now rendered as a
non-focusable section title above the list. All actionable rows (key
rows, "+ Add" sentinel) render at the same indent column. Agent override
sections still have focusable headers because they expand/collapse;
their content rows align with workspace-level rows for visual consistency.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): move default_agent to Agents tab; remove Last used from General
The General tab no longer shows the Default agent or Last used rows —
both were read-only and added clutter. Default agent is now set on the
Agents tab via the * key: cursor on an allowed agent and press * to set
it as default, or press * on the current default to clear it. Disallowing
the current default agent (Space) also clears the default. The default
agent is marked visibly in the Agents-tab list. Footer hints updated.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(op): session-scoped cache for picker structural metadata
Cache parsed Vec<OpAccount>/<OpVault>/<OpItem>/<OpField> results on
ConsoleState so repeated picker drilling within one `jackin console`
session is instant after the first call. Cache hits skip the op subprocess
entirely. Manual refresh via `r` keypress in any picker pane re-fires the
op call and updates the cache.
The cache stores only structural metadata. Field values from `op item get`
are never deserialized (RawOpField omits the JSON `value` key by design),
so credentials never enter the cache.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): picker scrolling and breadcrumb cleanup
Two picker UX fixes:
1. Scrolling now works in all four panes. The list widget's selection state
tracks viewport offset; navigating below the visible window scrolls the
list down. Repro: a vault with 20+ items.
2. Breadcrumbs drop the redundant trailing pane-name suffix. The pane
content makes the type self-evident — "Items" / "Vaults" / "Fields"
added noise. Titles now read "<email> → <vault>" instead of
"<email> → <vault> → Items".
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(op): show item subtitle (additional_information) in picker
1Password lets multiple items share a title (two "Google" entries with
different usernames, for example), and only the additional_information
subtitle distinguishes them. Capture op item list's additional_information
field on OpItem.subtitle, then render Item-pane rows as `<title> (<subtitle>)`
with the parenthetical in PHOSPHOR_DIM so the title still reads as primary.
The subtitle also participates in filter-as-you-type so operators can search
by username when the titles collide.
Items without an additional_information value (e.g., secure notes) render
with just the title, no trailing parens.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): drop per-row "● unsaved" markers on General and Secrets tabs
The Mounts and Agents tabs never rendered per-row dirty markers; the
footer's `S save workspace (N changes)` is the canonical unsaved-state
indicator. The General tab's Name/Working dir rows and the Secrets tab's
key rows were the outliers — adding visual noise that made the editor
inconsistent with itself.
Drop the markers from both render paths and the now-unused dirty
parameter from render_editor_row / render_secrets_key_line. Remove the
orphaned env_key_is_dirty helper.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): non-blocking event loop — spinner ticks and op results land without keystrokes
The console event loop was calling `event::read()` directly, which
blocks until a key/mouse event arrives. While blocked, the Braille
spinner stopped advancing and worker-thread results sat in the mpsc
channel undelivered — both updates only ran when the next event
unblocked the loop, making the picker feel frozen for seconds at a time
even after `op` had finished.
Switch to `event::poll(Duration::from_millis(50))` + conditional
`event::read()`. Loop runs at 20 Hz regardless of input. Add
`ManagerState::poll_picker_loads()` that drains the OpPicker channels
on every tick, so loading state advances as soon as the worker finishes
rather than waiting for keystroke pumping.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): source-picker modal — Plain text / 1Password choice on env add
Adding a new env var via Enter on the +Add sentinel now walks through:
EnvKey modal (name) → SourcePicker modal (Plain text / 1Password) →
EnvValue modal or OpPicker, depending on the choice. The 1Password
button is disabled when the op CLI isn't on PATH; the picker still has
the existing `P`-on-key-row shortcut for direct picker access.
ConsoleState gains an `op_available` flag set once at startup. Sentinel-
add invocations always go through the source picker; existing key-row
edits and P-on-key-row picks are unchanged.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): accept Shift modifier on letter shortcuts (caps-lock parity)
Caps Lock causes terminals to send letter keys with the SHIFT modifier
set. Existing match arms guarded by `key.modifiers == KeyModifiers::NONE`
silently rejected those, so operators with Caps Lock on saw `M`/`D`/`A`/
`P`/etc. no-op everywhere — D delete, A add, M mask/unmask, P 1Password,
and so on.
Replace the strict NONE check with a SHIFT-tolerant guard
(`(modifiers - SHIFT).is_empty()`) on every letter shortcut in the
editor, manager list, and widget handlers. Ctrl/Alt/Cmd modifiers are
unchanged.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): Secrets tab consistency — header label, empty state, "environment" wording
Three small consistency fixes to the Secrets tab:
1. Console header reads "edit workspace · <name>" (Edit) and
"create workspace" (Create) instead of "edit · <name>" / "new
workspace" — the missing "workspace" suffix was ambiguous.
2. Empty Secrets tab no longer renders "Workspace env" label or
"(no env vars)" placeholder — both add no information and aren't
present on Mounts/Agents tabs. The empty state is now just the
"+ Add environment variable" sentinel.
3. User-facing TUI text uses "environment" instead of "env" — the
abbreviation makes sense for CLI subcommand names but reads as
noise in the TUI. Sentinel text, modal titles, and footer hints
updated. CLI verbs and Rust identifiers (EnvScope, set_env_var,
WorkspaceConfig.env) are unchanged — the TUI is the only surface
where the full word matters.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): picker loading panel shows breadcrumb so operator knows what stage is loading
The picker title was emitting a full breadcrumb only after the load
completed, so during the 1-3s op subprocess the modal said just
"1Password" with no context — operator couldn't tell whether they were
waiting for vaults, items, or fields. Stage transitions advance at
request-time now (not result-time), so the title reflects the target of
the in-flight load: `<email> → <vault>` while items load, `<email> →
<vault> → <item>` while fields load, etc. Loading body messages adapt
the same way: "loading vaults from <email>", "loading items from
<vault>", "loading fields from <item>".
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): Q always opens an exit confirmation when not on the main screen
Pressing Q anywhere outside the main manager list now opens an "Exit
jackin'?" Y/N confirmation dialog. The operator can press N or Esc to
return to exactly where they were — useful when Q is accidentally hit
mid-flow. Q on the main screen still exits silently (today's behavior).
Q in a text input modal (EnvKey, EnvValue, picker filter) is treated as
input and never triggers the confirm. Routing is gated by
is_on_main_screen + consumes_letter_input checks at the top of the event
loop, so the dialog is the single chokepoint.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): field-load title shows parent context, item moved to loading body
While loading an item's fields, the picker title now shows the parent
context (`<email> → <vault>`) and the loading body names the item being
descended into (`loading ChainArgos Redshift (donbeave)…`). Previously
the title carried the item too, which made it ambiguous what was loading
versus already loaded. Principle: title = where you are, body = what
you're descending into. The same rule already applies to vault and item
loads (which were unaffected by this change).
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): block duplicate env keys live in the create-env-name modal
The EnvKey TextInput modal now carries the current scope's existing
environment-variable keys as a "forbidden" list. While the operator
types, an inline red warning shows when the name collides — and Enter
is blocked until the name is changed or cleared. Workspace-scope and
agent-scope sentinels each populate the forbidden list from their own
env map, so a key named "DB_URL" can coexist on workspace and agent
overrides without false positives.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): rename Secrets tab to Environments
The values managed in this tab aren't always secrets (DEBUG=1 is just a
config flag), so "Environments" is the more accurate label. Tab strip,
modal titles, and docs prose updated. Internal identifiers (EditorTab::
Secrets, SecretsRow, secrets_*) are unchanged — they describe the
codepath, not the user-facing label.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): op:// rows render as breadcrumb, never mask, ignore Enter
1Password references are paths, not credentials. Render them as a
breadcrumb (`<email>/<vault>/<item>→<field>` for multi-account,
`<vault>/<item>→<field>` for single-account) styled with dim/white/green
hierarchy so the field name reads as the addressable thing. Drop masking
on op:// rows entirely — the breadcrumb isn't a secret, no point masking
it. Block Enter from opening a text-edit modal on op:// rows; operators
delete and re-add via the source picker for changes. Plain-text rows
unchanged.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): per-row masking — M toggles the focused row only
Replace the global secrets_masked bool with an unmasked_rows BTreeSet
keyed by (scope, key). M toggles membership for the focused row;
everything else stays masked by default. Tab leave clears the set so
re-entering the tab returns to the all-masked baseline. Op:// rows
ignore the set entirely — they render as breadcrumbs (commit 31), not
masked values, so M is a no-op on them.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): mark op:// rows with ⚿ glyph; sort env keys alphabetically
Visual differentiation between plain-text and 1Password environment
variables: a left-edge ⚿ (lock-and-key) glyph appears on op:// rows,
plain rows render two blank spaces in the same column for alignment.
Both row types share one alphabetically-sorted list — no section break
— so the operator can scan keys in order and see the source at a
glance.
BTreeMap iteration already produces sorted keys, so the alphabetical
ordering is a confirmation pass with a regression-pinning test rather
than a code change to the iteration order.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): add per-agent override entry point on Environments tab
Operators can now add overrides for agents that don't yet have any —
previously the override section only appeared once `pending.agents`
already had an entry, so a fresh agent had no UI path to its first
override key.
A new sentinel row at the bottom of the Environments tab — `+ Add
per-agent override for ...` — opens an agent picker filtered to allowed
agents without existing overrides. Picking an agent creates the override
entry, auto-expands the new section, and lands the cursor on the
section's `+ Add <agent> environment variable` sentinel so the operator
can immediately add the first key. The sentinel hides itself when no
eligible agents remain.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): per-agent override picker drops into normal add flow
The picker for per-agent overrides was creating an empty section with a
"(0 vars)" header and a redundant per-agent "+ Add" sentinel inside as
soon as the operator chose an agent — looking messy if the operator
canceled out, and visually inconsistent with how workspace-level entries
are added. Now picking an agent opens the EnvKey modal directly with
Agent scope, and the rest of the chain (SourcePicker → value) is the
same as the workspace-level path. The agent's section materializes
organically when the first key/value commits; canceling at any modal
step leaves pending.agents untouched, so no orphan sections appear.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): TextInput shows red border on duplicate, hides Enter hint when invalid
Three small polish tweaks to the EnvKey TextInput modal so the visual
state always matches what Enter will actually do:
- Duplicate value → red border (DANGER_RED), inline ⚠ warning, footer
hint shows only `Esc cancel`.
- Empty value → default border (no error styling — empty isn't "wrong",
it's "not ready"), no inline warning, footer hint shows only
`Esc cancel`.
- Valid value → default border, footer hint shows `Enter confirm ·
Esc cancel`.
Enter blocks on both empty and duplicate. The widget gains an
`is_valid()` predicate that gates both the commit and the hint.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): scope-first add flow — pick All agents / Specific agent at start
Replaces the bottom-of-tab "+ Add per-agent override for ..." sentinel
landed in commit 34 with a scope question at the start of the add
flow. Press Enter on the workspace-level "+ Add environment variable"
sentinel; the new ScopePicker modal asks "All agents" or "Specific
agent". All-agents drops into the workspace add flow as before.
Specific-agent opens the agent picker (now showing every allowed agent
— no longer filtered by "doesn't yet have overrides", since operators
legitimately want to add more keys to agents that already have some),
then the EnvKey modal with Agent scope. The fast-path
"+ Add <agent> environment variable" inside an expanded agent section
is unchanged.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): AgentPicker layout follows the canonical list-modal pattern
The AgentPicker drifted stylistically from the OpPicker — different
title format (filter inlined), missing persistent filter row, "no
matches" placeholder, hard-coded "Enter launch" footer text. Operators
saw two visually distinct pickers for the same conceptual "pick from a
list with filter-as-you-type" interaction.
Refactor to match the OpPicker's layout: persistent Filter: row at
top, list body below, standardized footer. AgentPicker gains a
configurable confirm_label so the Enter-action word reads "launch" in
the launch-disambiguation path and "select" in the override-scope path
— same widget, two contexts, no truncation or wrong wording.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(rules): document the canonical TUI list-modal layout
Codify the list-modal layout pattern (filter row, list body, footer)
that the OpPicker, SourcePicker, and AgentPicker now share. Future
picker widgets follow the same shape so the TUI stays visually
consistent across modals. Sits alongside the existing TUI Keybindings
rule in RULES.md.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): Environments tab layout — blank-line between sections, [op] marker
Three small layout fixes for the Environments tab:
1. Add a blank-line spacer between the workspace section and each
agent override section. Cursor skips spacers on ↑/↓.
2. Replace the ⚿ glyph marker on op:// rows with a clearer `[op]` text
marker. Better terminal compat and immediately legible as "1Password".
3. Standardize the row prefix at 7 characters (2-char cursor column +
5-char marker column) so plain and op:// rows align across the tab.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): agent-section headers absorb ←/→ regardless of expanded state
Pressing → on an already-expanded section header (or ← on a collapsed
one) was falling through to tab cycling — operators saw the active
editor tab change unexpectedly when arrowing on `▼ Agent: <name>` or
`▶ Agent: <name>` rows. The header now absorbs ←/→ in both states:
acts on the row when relevant, no-ops otherwise, but never lets the
keypress reach the tab handler.
Codifies the convention as "Contextual key absorption" in
RULES.md § TUI Keybindings so future row types follow the same default.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): show environments on the main screen — workspace + per-agent
The main-screen workspace-manager preview gains a read-only
Environments block listing the highlighted workspace's env-variable
keys, sorted alphabetically with `[op]` markers on op:// references.
The Agents block now lists each agent's env overrides nested under the
agent name, with a "(no overrides)" placeholder for agents that don't
have any. Block order is General → Mounts → Environments → Agents so
the operator scans config-shaped data first and agent-specific things
last.
The preview stays read-only — `E` enters the editor for changes. Only
key names appear; values (plain or op://) never render.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): consolidate env preview — workspace + per-agent in one block
The main-screen Environments block now holds all env-related content:
workspace-level keys under an "All agents:" sub-header first, then each
agent's overrides nested under per-agent sub-headers. Agents without
overrides are omitted from this block — their absence is the signal.
The Agents block reverts to a lean summary (default + allowed agent
list) with no env nesting; per-agent env detail lives only in the
Environments block now.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): main-screen env preview is one flat alphabetical list with right-side agent label
Replace the workspace+per-agent sub-section layout with a flat,
alphabetical list. Each row shows the [op] marker, the env key name,
and (when scoped to a specific agent) the agent name on the right.
Workspace-level rows have an empty right column. Same env name
appearing at both workspace and agent level renders as two distinct
rows; sort puts workspace rows before agent rows when names tie.
The Agents block stays lean (default + allowed-list, no env nesting).
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): omit Environments preview block when workspace has no env vars
The main-screen Environments preview block was rendering an empty
"(no environment variables)" placeholder for workspaces with zero env
entries — visually noisy and unhelpful when most workspaces don't have
any. Now the block is omitted entirely when both workspace-level env
and per-agent overrides are empty; the Agents block fills the freed
space. Block appears as soon as any env entry exists at any scope.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): add 1-cell right padding on Environments preview agent label
Agent labels on per-agent env rows were rendering flush with the right
border. Subtract one cell from the right-padding budget so the label
ends one column before the border, matching the visual breathing room
of other right-aligned content.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): TextInput modal — 1-cell padding + subtle dim background on input field
Two small visual polish tweaks to the TextInput modal:
1. Input text now has a 1-cell left and right padding so it doesn't
sit flush against the modal border.
2. The input field gets a very subtle dim background tint
(Color::Rgb(20, 24, 22)) so the input region is visually distinct
even when empty — hinting "this is where you type".
Applies to every TextInput-modal use across the TUI (EnvKey, EnvValue,
workspace name, mount paths, etc.) since they all share TextInputState.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): TextInput dim background respects 1-cell padding on each side
Commit 47 painted the dim INPUT_BG_DIM background across the full input
row, including the 1-cell pads on each side. The pads should stay the
panel color so the dim band reads as the input region, not the whole
row. Move the bg block from `input_row` (full inner width) to
`textarea_area` (inset by 1 each side); the band now spans only where
input actually lives.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(op): strip trailing newline from `op read` output
`op read` appends a trailing newline as CLI convention. The resolved
env value was carrying that newline through to the container — when the
shell exported the var, `env` showed a blank line after the value.
Strip exactly one trailing `\n` (and an optional preceding `\r` for
Windows line endings) so internal newlines in legitimately-multi-line
secrets survive while the CLI's terminal newline is removed.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(reviews): land Claude and Codex detailed reviews of PR #171
Two independent reviews of the workspace-manager-tui PR's 50-commit
scope, captured side-by-side under docs/superpowers/reviews/. Used to
guide the final pre-merge sweep — keeping them tracked so the audit
trail of what was checked, by whom, and what was flagged stays visible
in git history alongside the work itself.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(config): validate candidate before rename + reject reserved/unknown-agent in env setters
ConfigEditor::save was renaming the temp file over the real config
BEFORE running the validation that AppConfig::load_or_init performs.
Two operator-reproducible bricking cases:
1. `jackin config env set FOO bar --agent ghost` wrote `[agents.ghost]`
without the required `git` field. Subsequent CLI commands failed at
load with "missing field `git`" before they could unset the value.
2. `jackin config env set DOCKER_HOST tcp://bad` succeeded silently,
then ALL future `config env *` commands failed at load with the
reserved-runtime-name validation.
Two-layer fix:
- Setter-level pre-flight: reject reserved env names (DOCKER_HOST and
the rest of the runtime list) and unknown agents at set_env_var
time, before any document mutation. Faster failure, clearer error.
- Save-level safety net: parse the candidate file via the same
validation load uses, BEFORE renaming over the real config. If
validation fails, remove the temp file and propagate the error;
the real config stays untouched.
Adds five integration tests covering the two reproduction paths plus
the save-level rollback invariant.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(console): refresh workspace list after manager save so launch routing isn't stale
ConsoleState.workspaces was a snapshot captured at console startup and
never refreshed. Manager edits (create/rename/edit/delete) rebuilt
ManagerState.workspaces but the launch dispatcher kept reading the
stale snapshot — so a renamed workspace appeared correctly in the list
but failed to resolve at launch, and edits to default_agent /
allowed_agents were ignored by launch routing until the next console
restart.
Chose Option B (derive launch choices from current AppConfig per-call)
because the call sites were tractable: only `dispatch_launch_for_workspace`
and the `LaunchWithAgent` arm in `run_console` consumed the snapshot,
and `WorkspaceChoice` is fully derivable from `(name, &AppConfig, &cwd)`.
Option B eliminates the duplication outright rather than papering over
it with refresh-after-save plumbing — there is no second source of truth
to keep in sync. `ConsoleState.workspaces: Vec<WorkspaceChoice>` and
`selected_workspace: usize` are dropped; a new
`pending_launch: Option<LoadWorkspaceInput>` pins the picker target
across the AgentPicker open→commit gap. `build_workspace_choice` is
exported from `console::state` and called per dispatch from current
config.
Adds four integration regression tests in tests/manager_flow.rs covering
create/rename/edit-default_agent/delete paths, plus two unit tests on
`build_workspace_choice` itself in src/console/state.rs (returns None for
unknown saved name; picks up default_agent from current config).
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(op): use op-provided `reference` field; parse 4-segment as vault/item/section/field
Two correlated correctness fixes for 1Password reference handling:
1. Use the `reference` field that `op item get --format json` returns
instead of synthesizing `op://Vault/Item/Field` from display names.
The synthesized form was wrong for fields living inside sections,
for items whose names contained `/` or whitespace, and for any case
where 1Password's authoritative serializer differs from naive
display-name concatenation. The new path always uses the string `op`
itself produces — no synthesis, no escaping bugs.
2. Fix `parse_op_reference` to interpret 4-segment references per the
official syntax (https://developer.1password.com/docs/cli/secret-reference-syntax/):
- 3 segments: `op://<vault>/<item>/<field>`
- 4 segments: `op://<vault>/<item>/<section>/<field>`
The previous "4 segments = account/vault/item/field" interpretation
was a jackin-specific extension that conflicted with op CLI itself.
Account scope stays separate state on the picker (`selected_account`
in OpPickerState) and is not encoded in the path.
The trust model is preserved: `RawOpField` continues to omit the
`value` field, and the safety test exhaustively destructures `OpField`
to fail compilation if anyone ever adds it. The destructure now also
covers the new `reference` field.
Cross-account references where the chosen vault lives outside the
op CLI's default account remain a documented limitation for this PR
— operator must ensure the relevant account is signed in.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(op): timeout op CLI probe; async account_list in picker constructor
OpCli::probe was using plain Command::output() with no timeout — a
wedged op (network stall, biometric held open, etc.) would freeze the
caller indefinitely. Route probe through the same channel-and-thread
timeout pattern that read/vault_list/item_list/item_get use; extract
spawn_op_with_timeout so probe (no JSON parse) and run_op_json (JSON
parse) share the spawn-and-wait primitive.
The picker constructor was calling account_list synchronously, which
blocked the TUI render loop before the spinner could paint. Move the
initial probe onto a worker thread (sharing a single Arc<dyn
OpStructRunner + Send + Sync> with the picker so test stubs are
captured instead of replaced by a fresh OpCli); the picker renders
the spinner immediately, account_list completes asynchronously, and
poll_load routes 0/1/multi to the right state via a new
handle_accounts_loaded helper.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(op): truncate_stderr respects UTF-8 char boundaries
truncate_stderr was slicing a String at byte index 4096. If that byte
fell inside a multi-byte UTF-8 character (e.g., op CLI emitting an
error in a non-ASCII locale, or any future op output containing
multi-byte glyphs), the slice would panic on an error path. Round the
truncation point down to the nearest char boundary at or before MAX.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): EnvValue modal allows empty values; target-specific input validity
TextInputState::is_valid was globally rejecting empty input, which is
correct for EnvKey/Name/Workdir but wrong for EnvValue — POSIX env
semantics distinguish VAR="" (set to empty) from unset VAR, and some
workloads use empty values to clear inherited defaults.
Add an allow_empty flag on TextInputState (settable via
new_allow_empty constructor); is_valid honors it for the empty-string
case while still rejecting duplicates. Wire the EnvValue modal sites
to construct via new_allow_empty. Other text-input targets keep the
non-empty default.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(tui): SourcePicker cycle dedup + align Secrets letter modifier guards
Two small consistency tweaks called out by review:
1. SourcePickerState had byte-identical cycle_forward and cycle_backward
methods (a 2-button toggle has only one transition). Collapse to a
single cycle() matching ScopePickerState's pattern.
2. The Secrets-tab letter shortcuts were using two different modifier
guards: m|M and p|P checked `(modifiers - SHIFT).is_empty()` (the
convention codified in RULES.md § TUI Keybindings), while d|D and a|A
checked `!modifiers.contains(CONTROL)` (accepted Alt/Cmd/Super
silently). Align d|D and a|A with the canonical pattern so all four
letter shortcuts behave the same way.
Both items are pure refactors — no operator-visible behavior change.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* test(op): picker async workers exercise the injected runner
Commit 55 switched the picker's runner field from Box<dyn OpStructRunner +
Send> to Arc<dyn OpStructRunner + Send + Sync> so worker threads share
the same runner instance the constructor was given. That removed the
test-vs-production gap where injected mocks were silently bypassed by
spawned threads (which were building fresh OpCli instances).
Land the regression tests that exercise this newly-reachable surface:
vault_list, item_list, and item_get all now pass through the injected
mock when called from worker threads. Future changes to the picker's
async loading paths can be caught by tests instead of only at runtime.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(reviews): mark PR #171 review findings as resolved
All findings in `pr-171-claude-detailed-review.md` and
`pr-171-codex-detailed-review.md` were addressed by commits 52–59
(workspace-list refresh, candidate-config validation, op://
reference fidelity, EnvValue empty-OK, async account_list, UTF-8
stderr truncation, async-worker test seam, SourcePicker dedup,
Secrets-tab modifier-guard alignment).
Move each finding to a "Resolved" section with the commit hash that
closed it so the markdown reflects current branch state and future
review passes don't re-flag closed concerns.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(op-picker): refresh stale comments about runner threading
Several comments in `src/console/widgets/op_picker/mod.rs` still
described the pre-commit-55 contract where worker threads built a
fresh `OpCli` regardless of the injected runner. That changed when
`OpPickerState::runner` switched from `Box<dyn OpStructRunner +
Send>` to `Arc<dyn OpStructRunner + Send + Sync>` and
`runner_clone_for_thread` became a thin `Arc::clone`. Reword:
- `OpPickerState::new` doc — "Boxes a fresh OpCli" → "Wraps a fresh
OpCli runner in an Arc". The constructor wraps in `Arc::new`, not
`Box::new`, and the wrapping is what enables the worker-thread
share.
- `enter_on_account_advances_to_vault_with_account_scope` test doc —
drop the "spawned thread runs through `runner_clone_for_thread`"
line and the parenthetical claim that the spawned thread builds a
fresh OpCli. Replace with an explicit pointer to the
`vault_list_uses_injected_runner_in_async_worker` test that
exercises the worker-thread path through the injected stub.
- "OpCache integration tests" header comment — drop the "(always
OpCli)" phrasing and reframe the section as "tests that
short-circuit before the worker spawn", which is what they
actually do today.
No code or behavior change.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(tui): consolidate pending env writes through one helper
Three Secrets-tab commit paths produced a final
`(scope, key, value)` triple and inserted it into `editor.pending`:
- `OpPicker` key-row commit (already routed through a helper);
- `EnvValue` text-modal commit (open-coded match);
- `EnvKey` commit's sentinel-picker fast path (open-coded match).
The two open-coded sites repeated the agent-scope auto-create +
`secrets_expanded.insert(agent.clone())` dance with the same
`pending.env.insert(...)` / `pending.agents.entry(...).or_default()`
shape. A future change to the contract (e.g. tracking dirty rows,
emitting a save-prompt dirty bit, recording last-edited timestamps)
would have to update three places in lockstep.
Rename `apply_picker_value_to_pending` → `set_pending_env_value`
since it's no longer picker-specific, expand the docstring to spell
out all three callers and the auto-expand-section invariant, and
route the two open-coded sites through it. Net `-13 +12` LOC; no
behavior change. All 1046 nextest cases still pass.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* chore(comments): trim verbose docs in operator_env + op_cache
Strip comments that restate the code, narrate commits, or rephrase
function/test names. Keep only WHY-level rationale: trust-model
invariants, external-system quirks, subtle behavior preserved
intentionally.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* chore(comments): trim verbose docs in widgets
Strip comments that restate the code, narrate commits, or rephrase
function/test names. Keep only WHY-level rationale: trust-model
invariants, external-system quirks, subtle behavior preserved
intentionally.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* chore(comments): trim verbose docs in manager state + input
Strip comments that restate the code, narrate commits, or rephrase
function/test names. Keep only WHY-level rationale: trust-model
invariants, external-system quirks, subtle behavior preserved
intentionally.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* chore(comments): trim verbose docs in manager render
Strip comments that restate the code, narrate commits, or rephrase
function/test names. Keep only WHY-level rationale: trust-model
invariants, external-system quirks, subtle behavior preserved
intentionally.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* chore(comments): trim verbose docs in console + config/editor
Strip comments that restate the code, narrate commits, or rephrase
function/test names. Keep only WHY-level rationale: trust-model
invariants, external-system quirks, subtle behavior preserved
intentionally.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* chore(comments): trim verbose docs in tests
Strip comments that restate the code, narrate commits, or rephrase
function/test names. Keep only WHY-level rationale: trust-model
invariants, external-system quirks, subtle behavior preserved
intentionally.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(operator_env): collapse repeated layer loops in validate + merge
`validate_reserved_names` repeated the same `for key in env.keys() { if
is_reserved(key) { offenses.push(...) } }` pattern four times, once per
EnvLayer variant. With the explanatory comments trimmed it was just four
copies of the same shape. Hoist into a local closure that takes the
layer label and the env map; the four call sites now read as a tiny
table of layer constructors.
`merge_layers` likewise had four byte-identical insertion loops; iterate
over the four layer references in priority order instead.
No behavior change. Test count unchanged at 1046.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(operator_env): extract build_attributed_layers helper
`resolve_operator_env_with` and `write_launch_diagnostic` each rebuilt
the same per-layer attributed map: global → agent → workspace →
workspace-agent, later wins. Two ~30-line copies of the same precedence
ladder, with comments now stripped, made the duplication obvious.
Hoist into `build_attributed_layers(config, agent, workspace)`. The
diagnostic site keeps the single extra step it needs (drop keys not in
`resolved`). The resolver site keeps the dispatch loop on top.
A regression in either copy of the ladder would have silently dropped
attribution for a layer; one source means errors and diagnostics are
guaranteed to agree on which layer supplied each key.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(op-picker): hoist Up/Down cursor cycle into a shared helper
Each of the four pane key handlers (Account/Vault/Item/Field) had a
byte-identical Up/Down arm that computed `(cur == 0 ? n-1 : cur-1)` /
`(cur+1 >= n ? 0 : cur+1)`. Eight near-identical bodies — readable in
isolation, painful to keep in sync if the wrap-around rule ever
changes.
Hoist into `OpPickerState::step_selection(&mut ListState, count, delta)`.
Each pane site now reads as one line per direction: compute filtered
count, call the helper.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(workspace): derive Default on WorkspaceConfig; collapse fixtures
`WorkspaceConfig` had every field defaultable (`String`, `Vec`,
`Option`, `BTreeMap`) but no `Default` derive, so 20+ fixtures across
console tests, two production builders, and the editor's `new_create`
spelled out all seven fields by hand. With the trimmed comments off,
the noise from the literals dominated the test bodies' actual intent
(which mounts? which agents? which env?).
Add `#[derive(Default)]`. Rewrite each fixture site to either
`WorkspaceConfig::default()` (when it really wants the empty
workspace) or struct-update syntax (`{ field: ..., ..Default::default() }`)
when it customizes one or two fields. Test bodies are now only as wide
as the field they care about.
Production sites updated to match: `EditorState::new_create` and
`WorkspaceCreate::build_workspace` use `WorkspaceConfig::default()` /
struct-update. No serialization or behavior change — the `Default`
impl produces the same all-empty value that every fixture was
spelling out.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(widgets): unify cursor-cycle helper across all list pickers
`agent_picker`, `github_picker`, `workdir_pick` each carried a
hand-rolled wrap-around cursor body inside their Up/Down arms — six
near-identical clones of the `(cur == 0 ? n-1 : cur-1)` /
`(cur+1 >= n ? 0 : cur+1)` pattern. `op_picker` had its own private
`Self::step_selection` helper covering the same four-pane case.
Hoist the helper to `console::widgets::cycle_select` (pub(crate)), drop
op_picker's private copy, and route all five pickers through it. The
`unwrap_or(0)` vs `map_or(0, …)` difference was non-load-bearing: every
caller already initializes `select(Some(0))` on a non-empty list and the
`count == 0` short-circuit covers the empty case identically.
Net: -45 LOC, single source of truth for wrap semantics, zero
behaviour change.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(workspace): finish WorkspaceConfig fixture sweep across remaining sites
Iteration 2 added `Default` to `WorkspaceConfig` and converted six TUI
test files that dominated the noise. Fifteen further sites still spelled
out the seven-field literal: production helpers (`resolve.rs`,
`planner.rs`, `mod.rs`), config CRUD (`config/mod.rs`,
`config/editor.rs`, `config/workspaces.rs`), state-machine fixtures
(`console/manager/state.rs` + four `input/*.rs` siblings),
operator-env reserved-name validators, app-context targeting, and two
integration test files (`manager_flow.rs`, `workspace_config_crud.rs`).
Each site now uses struct-update (`{ workdir: …, mounts: …,
..Default::default() }`) — keeping only the fields the test actually
cares about. No serialization or behaviour change: `Default` produces
the same all-empty values the literals were spelling out.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* test(manager_flow): collapse seed_config into seed_config_with_env
`seed_config` and `seed_config_with_env` were near-identical except for
one field — `env: BTreeMap::new()` vs `env: env_map`. Now that
`WorkspaceConfig::default()` lands the empty case, `seed_config`
delegates: `seed_config_with_env(paths, temp_dir, vec![])`.
Removes one duplicated workspace-skeleton fixture; both tests-paths
exercise the same code instead of two near-identical copies that could
drift.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* docs(reviews): drop fully-resolved Claude + Codex reviews
All findings from both reviews have been addressed across commits 52–59
and subsequent polish iterations. The full audit trail (file contents,
finding-to-commit mapping, every fix discussion) lives in the git log
itself; the standalone markdown files no longer carry information that
isn't already searchable in commit messages and PR history.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor: replace production unwraps with structural matches
Four `.unwrap()` sites in non-test code each guarded an invariant
already established by the immediately preceding `match` on a
collection's length, but expressed it as `into_iter().next().unwrap()`
or `as_ref().unwrap()`. Rewriting each as a slice-pattern match,
`Option::map_or`, or `swap_remove(0)` carries the invariant in the
type rather than in the prose, so a future refactor that drops the
length guard becomes a compile error rather than a runtime panic.
No behavior change.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(tests): finish WorkspaceConfig fixture sweep in manager_flow
Three remaining `WorkspaceConfig { … last_agent: None, env: …::new(),
agents: …::new() }` literals in manager_flow's helpers and tests still
spelled out the all-default tail by hand. The previous sweep landed
the pattern across the repo but skipped these three because each had
a non-default field interleaved (`allowed_agents`, `default_agent`)
that hid the trailing `..Default::default()` opportunity from a
literal grep. Hoist the trailer everywhere; the fixture intent
becomes "set these specific fields, defaults for the rest" rather
than mechanical zero-elision.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(manager): make manager-private submodules non-pub
`agent_allow`, `create`, and `github_mounts` are referenced only from
inside `console/manager/`. Drop their `pub` to express that boundary
in types — the public surface of the manager module remains `input`,
`render`, `state`, and `mount_info` (the last is reached from the
file-browser git prompt).
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(render): drop dead op_accounts chain + merge is_op double-scan
- Remove `op_accounts: &[OpAccount]` from `render_secrets_key_line`,
`render_secrets_tab`, and `render_editor`; the param was immediately
discarded with `let _ = op_accounts` and carried no information.
- Replace the two-scan pattern (`is_op_reference` then `parse_op_reference`
on the same string) with a single `parse_op_reference` call; use
`.is_some()` to derive the `[op]` marker. Eliminates one `starts_with`
scan per key row per frame.
- Update the two `render_secrets_tab` call sites in tests and the single
`render_editor` call site in `render/mod.rs`.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(nav): eliminate double secrets_flat_rows allocation on cursor move
`step_secrets_cursor_down` and `step_secrets_cursor_up` each called
`secrets_flat_rows` internally, duplicating the allocation in the same
keystroke that also called `secrets_flat_row_count` (another clone of the
same Vec). On every Down keypress in the Secrets tab, two full Vec<SecretsRow>
were allocated and dropped.
Fix: compute `rows` once at the call site, pass `&[SecretsRow]` into the
step helpers. Both helpers lose their `editor`/`config` parameters and the
internal `secrets_flat_rows` call. Remove `secrets_flat_row_count` entirely
(it was just `.len()` of the Vec) and drop it from the import and the now-
unreachable `Secrets` arm of `max_row_for_tab`.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(op-picker): borrow account id for cache writes instead of cloning
Add `selected_account_id_ref() -> Option<&str>` alongside the owned
`selected_account_id() -> Option<String>`. Use the reference form in the
three `poll_load` arms that write to the op cache (vaults, items, fields) —
they only need `&str` for the cache key, not an owned `String`. The owned
form is still used by `start_*_load` callers that move the value into a
spawned thread.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* style: rustfmt pass after render/editor.rs refactor
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): correct misleading footer hints + document op_picker invariant
Footer:
- Remove `M mask/unmask` from AgentHeader and add-sentinel footer arms;
`toggle_focused_row_mask` returns early for both row kinds (`_ => return`),
so advertising the key was a silent lie.
- Remove `M mask/unmask` from the SectionSpacer/None fallback arm (cursor
never lands there). Degrade to empty hint.
- Suppress "Enter pick working directory" hint on General tab row 1 when
`pending.mounts.is_empty()`; `open_editor_field_modal` silently does
nothing when there are no mounts to pick from.
- Remove dead `enumerate()` + `let _ = i;` in `footer_spans`.
OpPicker:
- Document the `self.accounts.len() > 1` multi-account invariant in
`handle_accounts_loaded` and the `handle_vault_key` Esc guard; the single-
account fast-path intentionally leaves `self.accounts` empty, making
the len check the canonical multi-account signal.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(render): thread live config into current-dir details pane
`render_current_dir_details_pane` was constructing `AppConfig::default()`
for both the agents-block height calculation and the `render_agents_subpanel`
call, so the Agents sub-panel on the "Current directory" row always rendered
zero agents regardless of what was actually configured.
Fix: add `config: &AppConfig` parameter and thread it in from the call site
in `render_list_body` (which already had `config`).
Also: document the `TempDir` drop requirement in `seed_config_with_env` so
a future test can't accidentally drop the guard before assertions complete.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(tui): cap startup op probe at 3s; gate P key + hint on op_available
Startup probe:
- Add `OpCli::new_probe()` with a 3-second timeout. `OpCli::new()` retains
the 30-second `OP_DEFAULT_TIMEOUT` for all operational calls (vault-list,
item-get, etc.) where blocking is expected. The startup probe only needs
to know whether `op` is installed and responsive; a false negative here is
acceptable — the picker renders its own error state on first open.
- Use `new_probe()` in `ConsoleState::new` so a network-stalled or
biometric-blocked `op` binary cannot freeze `jackin console` startup for
up to 30 seconds before the terminal is painted.
P key guard:
- Add `&& op_available` to the `P` arm in `handle_editor_key`; when `op`
is absent the key silently does nothing rather than opening a picker that
immediately shows a fatal error panel.
- Thread `op_available: bool` through `render_editor` → `contextual_row_items`
and conditionally omit the `P · 1Password` footer items on key rows and
add-sentinel rows when `op` is unavailable.
- Set `state.op_available = true` in the five `op_picker_*` integration tests
that exercise the `P` key path directly.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(confirm-save): off-by-one in required_height clips last diff line
The layout comment correctly counted 7 chrome rows (2 borders + top blank +
after-content blank + buttons + after-buttons blank + hint), but the
implementation returned N + 6. The modal was sized 1 row too short,
clipping the last changed-field line in every ConfirmSave dialog.
Fix: return N + 7, matching the documented layout. Update the test
assertion from 9 to 10 for a 3-line state.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): select just-saved workspace on return to list
After saving a workspace via the Esc → Save path (or after creating a new
one), the list cursor now lands on the workspace that was just written rather
than defaulting to the CWD row or the first entry.
The final on-disk name (`current_name`) is already correct in all cases —
the rename path updates it before the edit_workspace call — so it can be
looked up in the freshly-rebuilt ManagerState and used to set `selected`.
Adds a regression test with two workspaces to verify the cursor targets the
right entry, not just "index 1" by accident.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* refactor(confirm-save): move Create-mode env section into its match arm
The env-vars block for EditorMode::Create was appended after the match via
a second `if matches!(...Create)` guard, while the Edit arm correctly handled
its env section inline. This split made the Create arm appear complete when
it wasn't, risking duplication if someone added env logic inside the arm
without seeing the post-match block.
Move the env section into the Create arm directly, after the default-agent
block. Behaviour is identical; the structure now matches the Edit arm.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(op-picker): clear selected_item on Field→Item Esc back-navigation
`handle_item_key` Esc (Item → Vault) clears `selected_item = None`.
`handle_field_key` Esc (Field → Item) did not, leaving a stale item
reference on `self` while the operator was back on the Item pane.
The stale reference caused the breadcrumb to show the previous item name
during the first render frame after back-navigation. Add the clear to
match the established Esc-back pattern.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* feat(tui): s always exits to workspace list after successful save
Previously `s` used exit_on_success = false — it saved but kept the
operator inside the editor. The Esc → Save path already used
exit_on_success = true (returning to the list). This made `s` the odd
one out: the operator had to press Esc a second time to get back.
Change `s` to also use exit_on_success = true. Both save paths now
return to the workspace list after a successful write, with the cursor
positioned on the just-saved workspace (behaviour from an earlier commit).
Update two tests that were asserting the old "stays in editor" behaviour.
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
* fix(workdir-pick): scroll list so selected row stays visible
The picker rendered all choices as a Paragraph, which clips without
scrolling. On a deep directory tree (e.g. /home/user/Projects/repo
producing 4-6 ancestor choices) the highlighted row could disappear
off the bottom of the modal with no way to see it.
Apply an offset so the selected row is always within the visible
viewport: offset = selected.saturating_sub(visible - 1). Chain skip()
+ take() directly into the line-building iterator to avoid an
intermediate Vec allocation (clipp…
donbeave
added a commit
that referenced
this pull request
May 6, 2026
* docs(roadmap): iteration 1 — readability & modernization analysis Full first draft of READABILITY_AND_MODERNIZATION.md covering §0–§10: project inventory (72 files, 40k LOC), concept-to-location index (25 entries), documentation hierarchy proposal, source-code structural diagnosis (single-crate recommendation, 7 module-shape rules, hot-spot list), naming candidates, CI/tooling analysis, modernization candidates (13 entries with alternatives comparisons), AI-agent workflow (§8.1–§8.3: spec-driven development, superpowers alternatives, docs boundary), risks, and execution sequencing. All claims grounded in direct code reading (cited file paths and line ranges). All crate/tool recommendations include 2–3 alternatives. PR #171 branch read for AgentPicker, OpStructRunner, RawOpField, and RULES.md additions. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 2 — launch.rs deep analysis, TS strictness, preview workflow Four concrete improvements over iteration 1: - §4: Deep-read src/runtime/launch.rs (2368L) in full; mapped every function to exact line ranges and dependency graph; refined split proposal into 4 files (launch.rs ~120L, launch_pipeline.rs ~560L prod + 1200L tests, terminfo.rs ~110L, trust.rs ~60L); key finding: test module (1282L) exceeds production code (1083L). - §7.11: Discovered docs/AGENTS.md documents both TypeScript strictness blockers explicitly (rainEngine indexed access + astro-og-canvas optional-property types); verified rainEngine.ts at 5 line locations; rewrote recommendation as a concrete 4-step fix plan. - §6: Read preview.yml in full — Homebrew tap rolling-preview pipeline; flagged missing contributor documentation; resolved OQ3. - §2 concepts 4 & 6: Exact PR #171 data — TICK_MS=50 at console/mod.rs:90 with 20Hz rationale; compile-time safety test is an exhaustive struct destructure (not trybuild) at operator_env.rs:2055–2096. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 3 — operator_env.rs deep analysis, snapshot tests, OQ7 Three concrete improvements over iteration 2: - §4: Deep-read src/operator_env.rs (1569L); mapped every function to exact line ranges; identified two distinct clusters (op CLI subprocess layer lines 96-364 vs env layer resolution lines 365-808) with ~90L connective tissue; proposed module-directory split into mod.rs (~100L), client.rs (~280L), layers.rs (~470L), picker.rs (~250L PR#171); dependency graph shows no circular imports. - §7.5: Named first 3 concrete snapshot test targets with file:line citations — render_sentinel_description_pane (list.rs:306, zero state, ~10L), render_tab_strip (editor.rs:180, 4 enum variants, ~20L), render_mounts_subpanel (list.rs:408, 3 data-driven cases, ~30L). - OQ7 resolved: docs/package.json confirms astro-og-canvas ^0.11.1; docs/src/pages/og/[...slug].png.ts:~35 has `logo: undefined` which is the exact exactOptionalPropertyTypes conflict; fix is one line (omit the property). §7.11 and §9 updated accordingly. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 4 — config/editor.rs deep analysis, exact rustdoc coverage Two concrete improvements: - §4: Deep-read config/editor.rs (1467L); key finding: production code is only 503L (lines 1-503), tests are 963L (lines 504-1467 — nearly 2x); mapped all 18 public methods with exact line ranges into 5 domain groups; flagged create_workspace/edit_workspace validation-first architectural pattern as a refactor-preserve invariant; proposed 6-file module-directory split; priority note: lower than launch.rs/operator_env.rs since production code is already a reasonable size. - §1/§4/§7.6: Corrected rustdoc coverage from estimate "~28%" to exact count 37/90 files = 41%; identified cluster: console/manager/ and console/widgets/ (PR #171 additions) are well-covered; runtime/, app/, cli/ lag; updated all three occurrences consistently. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 5 — concrete brainstorm template, §10 step 4 ordering, §5 pruning Three concrete improvements: - §8.2: Added 17-line concrete docs/internal/agent-skills/brainstorm.md template with 6 fields (Purpose/When/Steps/Outputs/Done when/Overlap guard). The Done-when and Overlap-guard fields are the discipline gates missing from superpowers default brainstorming. Makes §8.2 immediately actionable instead of aspirational. - §10 step 4: Refined split ordering from sketch to priority-grounded sequence: config/types (4a) → manifest (4b) → config/editor 503L (4c) → operator_env 810L (4d) → app/dispatch (4e) → runtime/launch 1083L (4f last). Each sub-step has architectural notes and what-could-go-wrong. Flags the operator_env circular-dependency check for layers.rs→client.rs. - §5: Replaced 3 "leave as is" non-candidates (rows 10, 12, 15) with verified candidates from code reading: provision_claude_auth→apply_auth_forward (auth.rs:17), AuthProvisionOutcome→AuthForwardOutcome (instance/mod.rs), spawn_wait_thread→spawn_exit_watcher (operator_env.rs:202). - OQ5 resolved: instance/auth.rs read — only 210L production code, 585L tests (3x); no split needed; removed from hot-spot concern. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 6 — hot-spot splits, §8 cc-sdd pivot, op_cache, logging Four improvements: - §1: Hot-spot table rebuilt with production/test LOC split columns for all 22 files. Key finding: manifest/validate.rs is 145L prod/816L tests (exemplary testing, not a god file); app/mod.rs is 928L prod/22L tests (most genuine god file after launch.rs). Adds Priority column. - §8: Operator prefers existing tools over hand-rolled skill files. §8.1 recommendation pivots from hand-rolled (Option C) to cc-sdd (Option B). §8.2 rewrites comparison table — cc-sdd covers spec/plan/execute; TDD/debugging/review covered by existing project docs (TESTING.md, open-review-findings.mdx). Removes custom brainstorm template from iteration 5; no docs/internal/agent-skills/ dir needed. - §2 concept 14: Session-scoped op cache confirmed at src/console/op_cache.rs (252L, all production, no tests). Full detail: BTreeMap keyed by (account, vault_id, item_id) tuples, DEFAULT_ACCOUNT_KEY sentinel, invalidation methods, //! doc confirms metadata-only guarantee. - §7.14: New modernization entry — structured logging (log vs tracing vs current eprintln! approach). Recommendation: defer. Research in _research_notes.md. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 7 — MDX specs, §9 dep graph fix, §7.13 Renovate full entry Five concrete improvements: - §8.1/§8.3/§3: Operator direction — specs become public Starlight MDX pages at docs/src/content/docs/specs/ (draft: true while in-progress), not in docs/internal/specs/. Living source of truth updated with each code change. §8.3 boundary contract rewritten for public-spec model. §3 target shape updated to remove internal specs/ and add public specs/. - §9 R1: Corrected from "circular import risk" to "compilation-at-distance risk". Verified by grep: config imports workspace (config/mod.rs:1,5,6) but workspace does NOT import config. One-way dep: config → workspace. No circular risk; real risk is 30+ use path updates when AppConfig moves. - §10 step 2: Rewritten to match cc-sdd + Starlight MDX approach (was still describing hand-rolled docs/internal/agent-skills/ from pre- iteration-6 recommendation). Added draft page / lychee link-check caveat. - §7.13 Renovate: Full six-subheading entry replacing 2-sentence stub. Evaluated Dependabot and Renovate Cloud App; both rejected due to DCO sign-off constraint (RENOVATE_GIT_AUTHOR env var in renovate.yml:26 is not replicable in either alternative). Two low-cost tunings named. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 8 — lychee draft-page constraint, concept 18 correction Three improvements: - §8.1: Draft-page lychee constraint verified from docs/lychee.toml (full read). exclude_path has only 404.html pattern — no draft exclusion. Starlight draft pages exist in dist/ and are scanned by PR-time lychee check. Two fix options added: keep specs link-free, or add exclude pattern to lychee.toml. Added Astro sidebar requirement: manual config at astro.config.ts:50-103; autogenerate{ directory: 'specs' } pattern. - §2 concept 18: Corrected a wrong proposed move. AuthForwardMode → instance/auth.rs was incorrect — the type is used in 9 files, is a config field at config/mod.rs:89,96, and has serde Deserialize at line 74. Moving to instance/ would create circular dep. Corrected to: correctly placed, will move intra-module to config/types.rs in §10 4a. - §4 AuthForwardMode false alarm closed: confirmed NOT a §4 Rule 3 violation. Type is correctly in config because it IS a config value. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 9 — eprintln count, CODE_TOUR content, step 4a serde note - §7.14: grep-verified 96 production eprintln! calls across 16 files; breakdown by file; no rogue debug calls found, flip condition has not triggered - §2 concept 8: full 4-hop load chain with exact line numbers; documents the /__/ separator invariant in runtime_slug that prevents namespace collisions; CODE_TOUR column now specifies what the tour entry must explain - §10 step 4a: confirms AuthForwardMode serde impl is a plain impl<'de> block (not a derive), moves with the type to config/types.rs automatically Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 10 — dispatch_value scope, Renovate automerge, //! exemplar - §5 row 6: dispatch_value → resolve_env_value rename is 1 prod + 6 test call sites in a single file — lowest-cost rename in the table - §7.13: adds minimal safe automerge pattern for lockFileMaintenance only; explicitly excludes patch/minor bumps and SHA-pinned Actions - §4 Rule 7: expands //! exemplar analysis with direct quotes from env_model.rs:1-17 and isolates the three-element pattern Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): strip iteration annotations; reorder log chronologically READABILITY_AND_MODERNIZATION.md now presents a clean final view: - Remove all "(iteration N)", "(verified iteration N)", "read in full for iteration N", "updated iteration N" qualifiers throughout the body - Remove the Iteration counter from §0 meta - Resolved OQ markers simplified from "(resolved in iteration N)" to "(resolved)"; deferred OQ markers from "Tracked for iteration N" to "Deferred" _iteration_log.md now has entries in chronological order 1–10: - Was: 1, 3, 4, 5, 6, 7, 10, 8, 9, 2 - Now: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): research OpenSpec and IIKit; update §8 landscape _research_notes.md: - OpenSpec (Fission-AI/OpenSpec): brownfield-first SDD, three-phase propose/apply/archive, delta markers (ADDED/MODIFIED/REMOVED), living specs at openspec/specs/; /loop-compatible; recommended as cc-sdd complement for §4 module-split proposals - IIKit (intent-integrity-chain/kit): cryptographic .feature hash-locking, 8-phase workflow, Gherkin BDD chain, Tessl-only install; not recommended for jackin (no Gherkin step runner, Tessl lock-in, heavyweight for single-maintainer) READABILITY_AND_MODERNIZATION.md §8.1: - Adds Options E (OpenSpec) and F (IIKit) to the landscape and evaluation table with brownfield delta tracking and Rust/nextest compatibility rows - Adds OpenSpec complement note after the cc-sdd recommendation Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 11 — dtolnay correction, ci.yml detail, snapshot targets - §7.7 / §2 concept 25 / §10 step 3: correct false claim that dtolnay/rust-toolchain reads rust-toolchain.toml; action version is encoded in SHA rev; three sources require manual sync - §6 ci.yml row: expand from high-level to exact job steps; document gaps (no MSRV job, floating tags in build-validator, no doc job, main jackin binary never compiled in CI) - §7.9: grep-confirm all three snapshot target fn names; add exact signatures and private-fn access pattern (use super::*) with list.rs:720 as existing proof Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 12 — ClassSelector scope, lint table, app split - §5 row 5: ClassSelector → AgentClass rename is 138 prod call sites across 17 files — highest-scope rename in the table - §7.8: full lint table enumeration from Cargo.toml:47-75; notes that cast allowances are global despite TUI-scoped inline comment - §4 4e: app/mod.rs run() read in full; refined split from 1 file to 3 (dispatch.rs ~167L, workspace_cmd.rs ~438L, config_cmd.rs ~220L) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): remove resolved/no-action items from roadmap Keep only forward-looking actionable proposals: §2 concept table: removed 6 rows with "Stable"/"No change needed" proposals (RULES.md keybindings, hardline, mount planning, XDG paths, Docker builder, env-var ordering); renumbered 1-19 §5 naming table: removed hardline_agent row (leave-as-is, no action); rephrased load_agent row to focus on the //! doc action not the rename; renumbered to 1-14 §9 open questions: removed OQ3 (preview.yml, resolved — in §6), OQ5 (auth.rs split, resolved — no split needed), OQ7 (astro-og-canvas, resolved — fix in §7.11); renumbered to OQ1-4; merged TS strictness OQ back into the active list Cleanup: removed "resolved OQ5" tag from auth.rs hot-spot entry; removed "(resolved OQ7)" qualifier from §7.11 blocker label Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): update analysis baseline — PR #171 now merged to main Remove all "after PR #171 merges", "PR #171 branch", "pre-merge", and "not yet on main" qualifiers throughout. Specific changes: - §0: update baseline note from "treated as merged" to "now merged" - §1: remove branch-only qualifiers from file tree, module map, coverage count - §2: concept rows 1, 2, 4, 5, 6, 10, 11, 12, 14, 15 updated to reflect code now on main; row 12 op_cache current-state rating updated from requires-tribal-knowledge (pre-merge) to requires-grep (post-merge) - OQ1 (op_picker cache): updated from "deferred" to "read from main now" - §4, §5, §7: remove "(PR #171)" and "adds/branch" language - §10 step 1: remove "once PR #171 merges" condition Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> --------- Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
May 6, 2026
…183) * docs(roadmap): iteration 13 — AI code verifiability framing, config/types.rs full spec Primary goal shift: codebase must be verifiable for AI-generated code. - §0: replace generic description with explicit verifiability rationale (module contracts, localised concerns, types/behaviour separation) - §4 intro: add "Why structure matters for AI-generated code" section with audit-units table mapping each post-split file to one reviewable question - §4 4a: expand config/types.rs from description to full execution spec — exact type list, post-split mod.rs content, zero-change submodule guarantee (verified: agents.rs/persist.rs/workspaces.rs use super::T which resolves through mod.rs re-exports unchanged), impl-extension pattern already in use documented Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 14 — editor method map, app helpers, //! queue - §4 4c: config/editor.rs split is now execution-ready — complete 6-file method-to-file table with private helper placement verified (validate_candidate→io.rs, table_path_mut→mod.rs pub(super), auth_forward_str→agent_ops.rs, create_workspace delegates to AppConfig) - §4 4e: app/mod.rs split complete — all private helpers mapped (parse_auth_forward_mode_from_cli→config_cmd.rs, workspace_env_scope→workspace_cmd.rs, print_env_table note, remove_data_dir_if_exists→dispatch.rs) - §10 step 5: add //! priority queue — 10 files with draft content, prioritised by cold-landing impact and AI audit risk; selector.rs and instance/mod.rs explicitly document the /→__ invariant Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 15 — dep graph fix, trust.rs safety, OQ1 closed - §4 4d: correct operator_env dependency graph — layers.rs imports both mod.rs (OpRunner) AND client.rs (OpCli for non-injectable resolve_operator_env wrapper at line 797); still a valid DAG - §4 4f: verify trust.rs split safety — FnOnce injection pattern means launch_pipeline.rs has zero dependency on trust.rs; import chain documented; trust bypass audit now requires reading only ~60L - §9 OQ1 closed: op_cache.rs read in full — 4-level structure, per-level invalidation, no TTL/expiry (expiry handled at OpCli subprocess level), DEFAULT_ACCOUNT_KEY sentinel documented Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 16 — CommandRunner Rule 3, render/editor split, 4a/4c independence - Fix duplicate Rule 3 section introduced by previous edit; add docker.rs co-location note as third edge case (three edge cases, not two) - Add render/editor.rs as new Rule 5 violator: 1666L post-PR #171 (was listed as 782L); propose 6-file tab-by-tab split with auditability note on the security-adjacent Secrets tab - Add §10 execution-order note: 4a and 4c are independent — editor.rs imports AppConfig via crate::config re-exports regardless of 4a order - Append iteration 16 log entry with confidence table and weakest sections Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 17 — instance/auth.rs audit, state.rs split, line count corrections - Add instance/auth.rs to //! priority queue at #4: four security invariants (0o600 perms, symlink rejection, TOCTOU-safe writes, macOS Keychain) documented in draft //! content - Add state.rs as new Rule 5 violator: 992L/628L production; 26+ types mixed with impl blocks; propose 5-file types/behavior split - Correct stale line counts: render/list.rs 1122→1989 (PR #171 added render_environments_subpanel); state.rs 865→992; priorities upgraded - Fix §7.9 snapshot function line refs: sentinel_description_pane 306→332, mounts_subpanel 408→433, render_tab_strip 180→269, test ref 720→944 - Renumber //! priority queue to 11 entries (was 10) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 18 — agent_allow OQ2 closed, render/list.rs split proposal - Close OQ2: agent_allow.rs read in full — 55L, correct //! doc, design sound; serves as model for //! priority queue pattern - Add render/list.rs as new Rule 5 violator: 668L production (PR #171 added render_environments_subpanel); propose 3-file split (mod.rs, details.rs, subpanels.rs); note import-path change for agents_block_agent_count - Update §1 module map: agent_allow.rs entry corrected with size/API Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 19 — input/editor.rs critical correction, split proposal - Correct input/editor.rs: 2349L total (was 1304L), 1141L production (was 547L) — PR #171 added Secrets-tab handlers; pub(super) fn handle_editor_modal at line 618 was invisible to previous grep pattern; now the largest production file in the codebase; priority → Critical - Correct input/save.rs: 1472L total, 661L production (was 567L) - Add 5-file split proposal for input/editor.rs: mod.rs (two dispatchers), secrets.rs (~500L AI-generated Secrets-tab), agents.rs, mounts.rs, general.rs - Update key insight paragraph naming input/editor.rs as largest production file Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 20 — console splits in §10, MSRV evidence, animation.rs verdict - Add console/manager/ as §10 Step 4f group with 5 sub-steps in priority order; rename existing 4f (launch.rs) → 4g; add circular-import risk note for ManagerStage/EditorState split sequencing - Analyze tui/animation.rs: 582L all-production, no split needed (banner_grid is a tightly-coupled rendering loop); section comments compensate for missing //! - Partially close OQ3: u64::is_multiple_of (stabilized 1.86) found in animation.rs; within declared MSRV 1.94; full cargo +1.94.0 check deferred (toolchain unavailable) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 21 — input/save.rs split, //! queue fix, save.rs corrections - Add input/save.rs split proposal: 4 pub(super) fns discovered; 3-file split (mod.rs + flow.rs + preview.rs); no cross-dependency between flow and preview groups; §10 4f-v updated from Optional to concrete plan - Fix //! queue preamble: "first 10 files" → "first 11 files" - Correct save.rs module map (1418→1472L, correct key exports) and hot-spot table note (begin_editor_save ~280L → ~118L; commit_editor_save is the Phase 2 partner at ~149L) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 22 — input/list.rs and mount_info.rs analysis - Analyze input/list.rs: 214L production (tests at 215); has //! doc; two focused pub(super) fns; no split needed; Low priority; correct module map - Add mount_info.rs to hot-spot table: 277L production; Low priority; has //! doc; correct module map with 3 public enums + inspect fn - Fix stale §2 diagnosis note: docs/internal/roadmap/ now exists Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 23 — audit units table +5 console rows, input/mod.rs corrected - Expand audit units table from 8 to 13 entries: add state/types.rs, state/editor.rs, input/editor/secrets.rs, render/list/subpanels.rs, input/save/preview.rs — all targeting PR #171 AI-generated console code - Add PR #171 context note linking 5 new entries to AI-generated code concern - Correct input/mod.rs module map: 369L, add InputOutcome enum to exports - Verify rust-toolchain.toml absence; §7.7 and §2 concept 25 already correct Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 24 — render/mod.rs analysis, //! exemplars table, EditorTab confirmed - Add §4 Rule 7 positive exemplars table: 7 files with //! docs graded 1-element (render/mod.rs), 2-element (input/save.rs etc), 3-element (env_model.rs, agent_allow.rs); PR #171 docs-discipline pattern noted - Correct render/mod.rs module map: 421L; FooterItem + palette constants + render_header + centered_rect_fixed added to key exports - Confirm EditorTab variants: General, Mounts, Agents, Secrets (Rust enum) vs "Secrets / Environments" (UI label); /stub qualifier already removed Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 25 — too_many_lines recount, FooterItem PR, MountConfig caveat - Correct too_many_lines count: 13 across 8 → 16 across 11 files (PR #171 added 5 suppressions in console/manager); add full breakdown table; update all 3 occurrences in roadmap - Fix FooterItem PR reference: #165 → #166 (confirmed by git log --follow) - Add MountConfig → MountSpec rename caveat to §7.5 snapshot test description Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 26 — console/mod.rs and op_picker/render.rs analyzed - Add console/mod.rs to hot-spot table: 406L/307L production (Low); correct module map from ~200 → 406L; note missing //! doc with ConsoleStage design block comment worth promoting - Add op_picker/render.rs to hot-spot table: 865L/545L production (Medium); PR #171 AI-generated; 14 functions in two logical groups (entry/helpers vs level renderers); split into levels.rs proposed - Correct 3 stale ~200L estimates for console/mod.rs across roadmap Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 27 — op_picker/mod.rs discovery, render split, operator_env correction - Add op_picker/mod.rs to hot-spot table: 1712L/775L production (High); PR #171 AI-generated; OpPickerState types+behavior split opportunity; has 7-line //! doc; module map split into two rows (mod.rs + render.rs) - Add op_picker/render.rs 2-file split proposal: render.rs (coordinator) + render_pane.rs (pane/level renderers); no cross-dependency confirmed - Correct operator_env.rs total: 1569→2130L (880L production); update 4 occurrences across hot-spot table, ASCII tree, §4 analysis Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 28 — op_picker/mod.rs 3-file split, count corrections - Add op_picker/mod.rs formal 3-file split: loading.rs (async load family ~120L) + keys.rs (4 level key handlers ~315L) + mod.rs (types/constructors) - Correct "24 files" → "28+" for 500L threshold count - Update total LOC: ~40,664 → ~43,587 (2 occurrences, with provenance note) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 29 — op_picker execution order + file_browser analysis - §10 Step 4f: expand from 5 to 7 sub-steps; add 4f-vi (op_picker/mod.rs → mod.rs + loading.rs + keys.rs) and 4f-vii (op_picker/render.rs → render.rs + pane.rs); document impl-extension and import-path caveats - §4 //! exemplars: add file_browser/ subsystem analysis — all 5 files have //! docs, no file exceeds ~350L production; classified as exemplar (not a split candidate); document git_prompt.rs coupling-density justification and input.rs as 28-file false positive (144L production) - §1 module map: expand single file_browser/ row to 5 individual rows with production LOC and dominant concern per file Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 30 — challenge split-first thesis, fresh LOC corrections - §4: Add "Alternative thesis: documentation-first verification" — challenges the two core assumptions behind file splitting (files-as-audit-unit and file-size-as-context-constraint); adds 7-criterion comparison table vs structure-first approach; introduces phased combined recommendation: Phase 1 = doc sprint (//! contracts + specs/ for 3 subsystems, 2-3 PRs, zero structural change); Phase 2 = splits only for >600L production files (reduces scope from 14+ to 4 files); Phase 3 = workspace if LOC > 150K - Fix stale LOC: app/mod.rs 951→979, config/editor.rs 1467→1548 (7 and 8 locations respectively; verified by fresh find|xargs wc -l scan) - §1 module map: add agent_picker.rs (436L), scope_picker.rs (201L), source_picker.rs (244L) — all PR #171 additions with //! docs Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 31 — fix 600L→800L threshold error, correct LOC - §4 alternative thesis: correct ">600L production → 4 files" claim introduced in iteration 30; re-verified all 9 candidate files via #[cfg(test)] line position; threshold must be >800L to get exactly 4 files (9 exceed 600L); add verification table with test-start lines - Production LOC corrections (5+ locations each): launch.rs 1085→~1077, operator_env.rs 810→~880, app/mod.rs 928→~957, config/editor.rs 503→~584 - §2 OpPicker row: replace vague "no entry yet" with confirmed gap: PROJECT_STRUCTURE.md line 53 still lists pre-PR#171 widget set (10 named); omits op_picker/, agent_picker.rs, scope_picker.rs, source_picker.rs and pre-dates the manager/ sub-structure split Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 32 — two-tier spec arch, behavioral spec template - §8.1: Add two-tier spec architecture table distinguishing feature specs (public Starlight MDX, user-facing) from behavioral specs (internal docs/internal/specs/, for AI code verification) — resolves contradiction between §4 (which said docs/internal/specs/) and §8.1 (which said "no longer needed; specs are public") - §8.1: Add concrete behavioral spec template for op_picker/ with state machine table and 3 INV invariant entries each with a grep-executable "Verify by:" command; template directly usable for the 3 Phase 1 specs - §8.1: Remove erroneous "docs/internal/specs/ no longer needed" claim - Confirmed render/editor.rs ~736L and render/list.rs ~668L production (no interspersed production code — all test blocks follow consecutively) Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 33 — executive summary, §0 correctness - §0: Add executive summary (~300 words) with core problem, 3-phase recommendation, key counter-argument, and navigation table pointing to §2/§4/§7/§8/§10 by question — resolves the meta-irony of a readability roadmap with no entry-point orientation - §0 item 2: "1569-line monolith" → "2130-line monolith" (operator_env.rs current verified size; stale reference was in the first section readers see) - §0 item 3: Add "(selective)" qualifier and explicit note that standard Rust co-locates struct+impl — impl-extension pattern is justified only for files >800L production, not as a universal rule Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 34 — spec priority reorder, §10 Phase 1 track - §0 + §4 Phase 1: Prioritize runtime/launch.rs behavioral spec (no //! doc, ~1077L production, critical path — all jackin load failures trace here); drop config/editor.rs from Phase 1 (its 963L test suite already serves as behavioral spec — tests are behavioral examples); reduce Phase 1 from 3 specs to 2 specs; add reasoning for the priority ordering - §10 Step 2: Split into two parallel tracks — Track A (cc-sdd tooling setup) + Track B (Phase 1 behavioral spec authoring); Track B includes specific INV invariants to capture for runtime/launch.rs grounded in reading the actual function structure (step comment positions); adds sequencing rationale: spec must precede structural splits Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 35 — verified INV entries for runtime/launch.rs Read load_agent_with lines 553-892 in full. Replaced 3 draft INVs from iteration 34 (inferred from step comment positions) with 5 verified INVs citing exact line numbers: - INV-1: trust gate (line 594) precedes image build (line 736) - INV-2: container name claimed (line 754) between image build and network - INV-3: token verified (line 763) before network creation (line 827) - INV-4: render_exit called at lines 886 AND 890 (all exit paths) - INV-5: cleanup disarm semantics — Running→disarm, clean exit→cleanup, crash→disarm (explains jackin hardline compatibility) Corrected wrong line number: claim_container_name call is at 754, not 918 (918 is the function definition). Each INV has a grep-executable Verify by. Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 36 — CI gate for PROJECT_STRUCTURE.md freshness §3: Add "Preventing future PROJECT_STRUCTURE.md staleness" subsection with three concrete options: - Option A: CONTRIBUTING.md rule (necessary but insufficient) - Option B: ci.yml git-diff-scoped shell check (recommended) — only checks files added in the current PR so it doesn't require fixing existing stale entries before merging; greps for module directory name in prose - Option C: Structured TOML module registry (over-engineered for scale) Includes concrete YAML snippet for Option B grounded in the check:repo-links.ts pattern already established in docs/scripts/ Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iterations 36-37 — CI gate + greenfield workspace architecture Iteration 36: - §3: Add "Preventing future PROJECT_STRUCTURE.md staleness" subsection with 3 options (CONTRIBUTING.md rule / ci.yml git-diff check / TOML registry); recommend Option B (git-diff-scoped YAML step) with concrete snippet grounded in existing check:repo-links.ts pattern from docs/scripts/ Iteration 37 (operator directive: greenfield Rust structure): - §4: Add "Greenfield architecture — ideal structure for a growing project" section based on verified cross-module dependency graph (grep iteration 37) - Confirms dependency tiers: workspace/manifest/docker/paths/selector = Tier 0; config/tui/instance = Tier 1; operator_env/runtime/repo = Tier 2; console = Tier 3 - Key finding: workspace/ is LOWER-level than config/ (config re-exports workspace types at lines 5-6); ideal naming inverted in greenfield (jackin-core > jackin-config) - Documents ideal 6-crate workspace: jackin-core, jackin-config, jackin-tui, jackin-runtime, jackin-console, jackin-shell + thin binary - Notes console/ has NO runtime/ import — cleanest pre-existing crate boundary - Bridge: incremental splits (4a, 4d, 4g) are pre-work toward workspace migration Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 38 — Rust workspace standards, community evidence Ground workspace recommendation in real-world project research: - ripgrep (9 crates), gitui (5 crates) went workspace due to library consumers - starship and fd-find stay single-crate at 1M+ LOC — no library use case - jackin (43K LOC, no external consumers) maps to starship/fd pattern → single-crate is community-standard; "stay single-crate" recommendation confirmed Update greenfield workspace structure to follow matklad's pattern: - Virtual manifest at root (no [package] in root Cargo.toml) - Flat crates/ directory (not nested); crate names match folder names - version = "0.0.0" for unpublished internal crates - Add inline dep comments to each crate in the ASCII structure Add research notes: ripgrep/starship/gitui/fd-find Cargo.toml findings + Cargo workspaces reference + matklad "Large Rust Workspaces" (2021-08-22) Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): revise §7.9 + §3 — adopt per-directory README.md §7.9: Reverse previous "reject" recommendation to "adopt" per-directory README.md for major src/ module directories. Rationale: README.md is AI-native — Claude Code, Copilot, Cursor load it automatically on directory entry, giving AI agents orientation before they decide which file to open. PROJECT_STRUCTURE.md being confirmed stale removes the main argument for the "single root file" approach. Add three-layer documentation model table: - README.md: directory orientation (AI + human, on entry) - AGENTS.md: agent workflow rules (root, session start) - CLAUDE.md: @AGENTS.md pointer only — NEVER add content here - //! docs: file-level contracts (when reading/editing) Add specific README.md content targets for 7 directories (src/, src/runtime/, src/console/, src/console/manager/, src/console/widgets/, docs/, docs/internal/). §3 target document shape: Add per-directory README.md to proposed hierarchy; add docs/internal/specs/ explicitly; note CLAUDE.md design principle (single-line @AGENTS.md — never duplicate content). Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): internal docs are browsable — unified Starlight site Operator directive: internal docs (architecture, specs, ADRs, roadmap) should be browsable, not hidden filesystem files. They are a different TYPE of docs focused on implementation details and vision, published as a "Developer Reference" section of the Starlight site. §3 target document shape: - docs/internal/ moves into docs/src/content/docs/internal/ (Starlight pages) - Browsable at jackin.tailrocks.com/internal/ - Sidebar: "Developer Reference" group (collapsed by default) with sub-sections for architecture, code-tour, contributing, testing, decisions, specs, roadmap - Include astro.config.ts sidebar config snippet §8.1 two-tier spec distinction eliminated: - Feature specs and behavioral specs both live at docs/src/content/docs/internal/specs/ - Type expressed via spec_type: behavioral | feature frontmatter, not filesystem location - Both browsable and searchable via Starlight; AI agents can be pointed to URLs §8.3 + §4: - All docs/internal/specs/ paths → docs/src/content/docs/internal/specs/ - ADRs: docs/internal/decisions/ → docs/src/content/docs/internal/decisions/ (browsable) - README.md pointer for src/runtime/ updated to URL reference Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): §11 — modern Rust docs platform (future project) Add §11 capturing the vision for a modern docs.rs alternative with: - rustdoc JSON ingestion → Astro Starlight presentation - MCP server for AI agent queries (Context7 alternative for Rust) - Rust-specific query types: rust_get_context(), rust_find_impls(), rust_search_types() — things Context7 cannot provide - Comparison table vs Context7 - Architecture diagram (ingestion → processing → Starlight + MCP) - Name candidates: rustlight, ferrodoc, cargo-starlight / starlight.rs - Note that jackin's §7.15 gen-rust-api.ts pipeline is the intentional prototype for the platform's processing and presentation layers Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 39 — update §0, fix stale internal/ paths §0 executive summary: rewrite to reflect decisions from iterations 30-38: - browsable internal docs (jackin.tailrocks.com/internal/) - per-directory README.md adoption (§7.9 reversed) - CLAUDE.md = @AGENTS.md single-line pointer only - greenfield workspace architecture (matklad's virtual manifest pattern) - §11 future project: modern Rust docs platform / Context7-for-Rust - document size 1800+ → 2200+ Fix stale docs/internal/ bare paths not caught by iteration 38 sweep: - Mermaid diagram: INTERNAL_ROADMAP, INTERNAL_CODE_TOUR → Starlight paths - §7.10 ADRs: docs/internal/decisions/NNN-title.md → .mdx Starlight path - §10 Track B item 2: op-picker spec path → Starlight MDX Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): iteration 40 — §7.15 pipeline + Rule 4 pub audit §7.15 (new): rustdoc JSON → Astro Starlight API documentation pipeline - Three options: rustdoc HTML publish / rustdoc JSON + bun script (recommended) / rustdoc-json crate as Rust binary - Option B recommended: matches existing docs/scripts/ pattern, nightly isolated to separate CI step, zero effect on stable build - Key design: URL at /internal/api/, cross-links to behavioral specs, Starlight unified search, prototype for §11 future project - Pub(crate) note: gen-rust-api.ts can feed Rule 4 visibility audit - Recommend: adopt after Phase 1 //! sprint (value ∝ coverage) §4 Rule 4 pub discipline: replace estimated "50-100 items" guess with verified numbers from iteration 40 grep: - 257 bare pub items, 21 pub(crate), 61 pub(super) across 94 files - 0 uses of unreachable_pub lint — no enforcement gate - Top violators: operator_env.rs (17), tui/output.rs (13), planner.rs (8) - Add concrete Cargo.toml [lints.rust] snippet: unreachable_pub = "warn" - Revised scope: ~150-200 mechanical conversions (excludes entry points) Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): split research into 19 actionable items Delete _research_notes.md (no longer needed). Replace 2343L READABILITY_AND_MODERNIZATION.md with: - README.md: index of all 19 items with phase, ordering notes, links - READABILITY_AND_MODERNIZATION.md: lightweight research summary (63L) - items/ITEM-001 through ITEM-019: individual actionable items Items by phase: Phase 1 (low risk, no confirmation): ITEM-001..004, 006..011 Phase 1 (needs confirmation): ITEM-005, 016, 018 Phase 2 (structural splits, confirmation required): ITEM-012..015 Phase 3 (deferred): ITEM-017 (rustdoc pipeline), ITEM-019 (workspace) Each item has: summary, key files with line numbers, steps, what needs confirmation, and relevant research backing from the 40-iteration analysis loop. Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): migrate 19 items to Starlight reference/roadmap section Move all codebase health roadmap items from docs/internal/roadmap/items/ (plain Markdown, not browsable) to docs/src/content/docs/reference/roadmap/ (MDX pages, browsable at jackin.tailrocks.com/reference/roadmap/). Adds a new "Codebase health" sidebar group (Phase 1 → Phase 3) to astro.config.ts. Deletes the old items/ directory. Updates the internal README to redirect to the new location. Also adds codebase-readability.mdx — a new overview item that captures the overall readability/restructuring program with a recommended execution order: file splits first, then greenfield workspace, then per-directory README+AGENTS.md, then docs and specs. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): remove premature internal/roadmap/README.md The internal/ structure doesn't exist yet — it will be created as part of the roadmap items themselves. No need for a redirect stub now. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): remove READABILITY_AND_MODERNIZATION.md research archive All content has been distilled into the individual Starlight roadmap pages. The full 2343L research is preserved in git history at commit b7e9fc2. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): fix check:repo-links errors + remove iteration log - Replace plain code spans with <RepoFile> for validate.rs, mise.toml, Cargo.toml, and op_picker/mod.rs - Remove deleted READABILITY_AND_MODERNIZATION.md reference from codebase-readability.mdx - Delete _iteration_log.md (git history is the archive) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> * docs(roadmap): fix lychee false-positive link in move-contributing-testing The example redirect text contained a markdown hyperlink to a proposed future file path that doesn't exist yet. Changed to a code span. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> --------- Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Co-authored-by: Claude <noreply@anthropic.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
* feat(tui): extend EditorState and save path for Secrets tab
Add secrets_masked / secrets_expanded / pending_env_key fields to
EditorState, SecretsScopeTag type, EnvKey/EnvValue TextInputTarget variants,
DeleteEnvVar ConfirmTarget variant. Extend change_count with env diff
logic. Wire commit_editor_save to compute env diffs and call
ConfigEditor::set_env_var / remove_env_var before save(). Extend
build_confirm_save_lines with an "Env vars:" diff section.
The new TextInputTarget / ConfirmTarget variants are unreachable in this
commit; their handlers land in commit 2 (UI). Chose Option A: placeholder
unreachable!() arm in apply_text_input_to_pending so all type definitions
are established in commit 1. ConfirmTarget has no exhaustive-match site
so it needed no placeholder (Modal::Confirm uses target: _).
Removing Copy from TextInputTarget (EnvValue now carries a String) forced
minor follow-on changes: pass-by-reference at the apply_text_input_to_pending
callsite and `&Variant`-style comparisons in existing test assertions in
prelude.rs and editor.rs.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): implement Secrets tab render and input handlers
Replace render_secrets_stub with a sectioned, hierarchical render:
"Workspace env" header always visible, key rows with masked/unmasked
display, collapsible per-agent override sections, "+ Add" sentinels, dirty
markers. Wire input handlers: Enter (edit/expand/add per row kind), D
(delete confirm), A (add), Ctrl+M (mask toggle), Left on expanded headers
(collapse). Tab-cycle resets masking and collapses sections on leave.
EnvKey/EnvValue handlers in handle_editor_modal route the two-step add
flow; DeleteEnvVar handler removes the key from pending. Tab strip drops
"Secrets ⏳" placeholder label and dim/italic style.
Co-authored-by: Claude <noreply@anthropic.com>
* test(tui): Secrets tab unit and integration tests; update console docs
Add unit tests for change_count env-diff variants on EditorState and
render-buffer tests for masking and section expand/collapse. Add seven
integration scenarios in tests/manager_flow.rs covering edit, delete,
add, masking toggle, section expand/collapse, dirty detection, and the
two-step add flow. Append a "Workspace editor — Secrets tab" section
to docs/src/content/docs/commands/console.mdx documenting the keybindings
and scope semantics.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): wire Right arrow to expand collapsed Secrets sections
The Secrets tab key-map advertises `→` as an alias for `Enter` on a
collapsed agent override header (symmetric with `←` for collapse), and
the docs landed in commit 3 say so. Commit 2 left this unwired — Right
fell through to the tab-advance handler before the Secrets dispatch
could see it. Add a guard at the top of the `Tab | Right` arm that, on
the Secrets tab, expands the section when Right is pressed on a
collapsed `AgentHeader`. Tab and Right-on-non-header still advance.
Update the integration test to exercise `→` instead of `Enter` so a
future regression of the guard is caught.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(op): add OpStructRunner trait and OpCli implementation for vault/item/field listing
Add OpStructRunner trait with account_list, vault_list, item_list, item_get
methods. Implement on OpCli using the same spawn-and-channel timeout pattern
as OpRunner::read. Add serde shapes (RawOpVault, RawOpItem, RawOpItemDetail,
RawOpField) that explicitly omit field values — the picker is a metadata
browser that must not deserialize secret values into memory. Includes unit
tests for JSON parsing, value-suppression, signed-out detection, and the
concealed flag derivation.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): 1Password vault/item/field picker modal (Ctrl+O from EnvValue input)
Add OpPickerState, OpPickerStage, OpLoadState types in
src/console/widgets/op_picker/. Add render function with three-pane
drill-down, filter-as-you-type, Braille spinner, and four failure-state
instructional displays. Wire Modal::OpPicker variant into modal_outer_rect,
render_modal, and handle_editor_modal. Intercept Ctrl+O in the EnvValue
TextInput arm of handle_editor_modal; restore original text on Esc-cancel.
Add Ctrl+O footer hint to contextual_row_items Secrets key-row arm. Calls
OpStructRunner from commit 5; field values are never deserialized.
Co-authored-by: Claude <noreply@anthropic.com>
* test(tui): op picker unit/integration tests; update console docs
Unit tests on the op picker state machine: filter narrowing, pane
advancement clearing the filter, Esc back-navigation through Vault → Item →
Field, concealed-first field sort, commit returning the op:// path.
Integration tests in tests/manager_flow.rs: Ctrl+O from EnvValue modal opens
OpPicker; Esc-cancel restores the EnvValue modal with original text intact.
Docs: Ctrl+O row in Secrets key-map, plus a new "1Password picker" sub-
section documenting requirements and failure-state messages.
Co-authored-by: Claude <noreply@anthropic.com>
* chore(rules): add TUI Keybindings section to RULES.md
No Ctrl/Alt/Cmd modifiers in TUI keybindings; actions on rows rather than
inside text modals. Applies project-wide starting with the Secrets tab
cleanup that follows in subsequent commits.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(tui): Ctrl+M → M for Secrets-tab mask toggle
Replace the Ctrl-modified mask binding with plain M, consistent with the
new RULES.md TUI Keybindings rule. Update footer hints, docs, and tests.
The ctrl_key test helper stays for now; it goes away in the next commit
once the Ctrl+O picker redesign also no longer needs it.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(tui): drop Shift+Tab support; tighten RULES.md rule
The TUI Keybindings rule now also forbids Shift modifiers, not just
Ctrl/Alt/Cmd. Shift+Tab is a chord; rule is "single-key inputs only".
Drop the standalone BackTab arm in the editor's tab-cycle handler
(KeyCode::Left already implements the previous-tab fallthrough). Drop
BackTab from confirm/save_discard/mount_dst_choice/confirm_save/git_prompt
alternations — Left and h still cover prev-direction. Replace BackTab
with Left in test drivers. Update inline comments.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(tui): move 1Password picker to row-level P action
Remove the Ctrl+O interception inside the EnvValue text modal. P on a
Secrets-tab key row opens the picker directly; on commit, the chosen
op:// reference is written straight to that key's pending value (no
intermediate text modal). P on the +Add sentinel opens the picker first;
the EnvKey modal then collects the key name with the path pre-stashed
on EditorState. Drop saved_target/saved_label/original_value from
OpPickerState — the picker no longer reconstructs the EnvValue modal.
Update footer hints, docs, and tests; remove the ctrl_key test helper
now that no caller remains.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): Modal::AgentPicker — inline agent disambiguation popup
Replace the full-screen ConsoleStage::Agent picker with a Modal::AgentPicker
overlay on the manager list. Delete draw_agent_screen, the legacy
handle_event Agent branch, ConsoleStage::Agent variant, ConsoleState's
selected_agent / agent_query fields, and filtered_agents method. Three-
branch launch logic: default_agent set → direct launch; single eligible
agent → direct launch; multiple eligible → popup. Add AgentPickerState
widget anchored on ManagerState.list_modal. New InputOutcome::
LaunchWithAgent variant. Tests updated: 5 legacy state/input tests
deleted, 5 new manager_flow integration tests for the popup, 3 unit
tests for the picker widget.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(op): accept account_uuid alongside id in `op account list` output
`op account list --format json` against op CLI v2.x reports each account
with an `account_uuid` key, not `id`. The picker probe was failing in the
field with "missing field `id` at line 7 column 3" when an operator with
multiple accounts pressed P → 1Password. Add a serde alias on RawOpAccount
so either shape parses. Add a unit test using a real-shape JSON fixture.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(spec): amend workspace-manager-tui-design for 2026-04-25 changes
Append a dated amendment block to the spec capturing how the Stage 3
implementation diverged from the 2026-04-23 design: manager-as-default
landing (not an excursion), single-variant ConsoleStage with
Modal::AgentPicker replacing the full-screen Agent stage, three-branch
launch logic, the no-Ctrl/no-Shift keybinding philosophy, and the
1Password picker moving from a Ctrl+O modal-internal action to a P
row-level action on the Secrets tab. Preserves the original record;
amendments captured separately.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(op): multi-account 1Password picker
Add an Account pane as the first stage of the 1Password picker, visible
only when the operator has 2+ accounts signed in to the op CLI. Single-
account setups skip the pane entirely (no UX regression). All downstream
op calls (vault_list, item_list, item_get) thread the chosen account_uuid
via --account <id> so cross-account drilling works correctly.
OpAccount gains email + url fields for human-readable display. RawOpAccount
serde shape uses #[serde(default)] on optional fields for tolerance against
older op versions. Esc from Vault returns to Account when multi-account;
otherwise closes the picker as before.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): Secrets tab — skip Workspace label as focusable row, align indents
The "Workspace env" section label was incorrectly focusable, so the
cursor landed on it when the tab opened. The label is now rendered as a
non-focusable section title above the list. All actionable rows (key
rows, "+ Add" sentinel) render at the same indent column. Agent override
sections still have focusable headers because they expand/collapse;
their content rows align with workspace-level rows for visual consistency.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): move default_agent to Agents tab; remove Last used from General
The General tab no longer shows the Default agent or Last used rows —
both were read-only and added clutter. Default agent is now set on the
Agents tab via the * key: cursor on an allowed agent and press * to set
it as default, or press * on the current default to clear it. Disallowing
the current default agent (Space) also clears the default. The default
agent is marked visibly in the Agents-tab list. Footer hints updated.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(op): session-scoped cache for picker structural metadata
Cache parsed Vec<OpAccount>/<OpVault>/<OpItem>/<OpField> results on
ConsoleState so repeated picker drilling within one `jackin console`
session is instant after the first call. Cache hits skip the op subprocess
entirely. Manual refresh via `r` keypress in any picker pane re-fires the
op call and updates the cache.
The cache stores only structural metadata. Field values from `op item get`
are never deserialized (RawOpField omits the JSON `value` key by design),
so credentials never enter the cache.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): picker scrolling and breadcrumb cleanup
Two picker UX fixes:
1. Scrolling now works in all four panes. The list widget's selection state
tracks viewport offset; navigating below the visible window scrolls the
list down. Repro: a vault with 20+ items.
2. Breadcrumbs drop the redundant trailing pane-name suffix. The pane
content makes the type self-evident — "Items" / "Vaults" / "Fields"
added noise. Titles now read "<email> → <vault>" instead of
"<email> → <vault> → Items".
Co-authored-by: Claude <noreply@anthropic.com>
* feat(op): show item subtitle (additional_information) in picker
1Password lets multiple items share a title (two "Google" entries with
different usernames, for example), and only the additional_information
subtitle distinguishes them. Capture op item list's additional_information
field on OpItem.subtitle, then render Item-pane rows as `<title> (<subtitle>)`
with the parenthetical in PHOSPHOR_DIM so the title still reads as primary.
The subtitle also participates in filter-as-you-type so operators can search
by username when the titles collide.
Items without an additional_information value (e.g., secure notes) render
with just the title, no trailing parens.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): drop per-row "● unsaved" markers on General and Secrets tabs
The Mounts and Agents tabs never rendered per-row dirty markers; the
footer's `S save workspace (N changes)` is the canonical unsaved-state
indicator. The General tab's Name/Working dir rows and the Secrets tab's
key rows were the outliers — adding visual noise that made the editor
inconsistent with itself.
Drop the markers from both render paths and the now-unused dirty
parameter from render_editor_row / render_secrets_key_line. Remove the
orphaned env_key_is_dirty helper.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): non-blocking event loop — spinner ticks and op results land without keystrokes
The console event loop was calling `event::read()` directly, which
blocks until a key/mouse event arrives. While blocked, the Braille
spinner stopped advancing and worker-thread results sat in the mpsc
channel undelivered — both updates only ran when the next event
unblocked the loop, making the picker feel frozen for seconds at a time
even after `op` had finished.
Switch to `event::poll(Duration::from_millis(50))` + conditional
`event::read()`. Loop runs at 20 Hz regardless of input. Add
`ManagerState::poll_picker_loads()` that drains the OpPicker channels
on every tick, so loading state advances as soon as the worker finishes
rather than waiting for keystroke pumping.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): source-picker modal — Plain text / 1Password choice on env add
Adding a new env var via Enter on the +Add sentinel now walks through:
EnvKey modal (name) → SourcePicker modal (Plain text / 1Password) →
EnvValue modal or OpPicker, depending on the choice. The 1Password
button is disabled when the op CLI isn't on PATH; the picker still has
the existing `P`-on-key-row shortcut for direct picker access.
ConsoleState gains an `op_available` flag set once at startup. Sentinel-
add invocations always go through the source picker; existing key-row
edits and P-on-key-row picks are unchanged.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): accept Shift modifier on letter shortcuts (caps-lock parity)
Caps Lock causes terminals to send letter keys with the SHIFT modifier
set. Existing match arms guarded by `key.modifiers == KeyModifiers::NONE`
silently rejected those, so operators with Caps Lock on saw `M`/`D`/`A`/
`P`/etc. no-op everywhere — D delete, A add, M mask/unmask, P 1Password,
and so on.
Replace the strict NONE check with a SHIFT-tolerant guard
(`(modifiers - SHIFT).is_empty()`) on every letter shortcut in the
editor, manager list, and widget handlers. Ctrl/Alt/Cmd modifiers are
unchanged.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): Secrets tab consistency — header label, empty state, "environment" wording
Three small consistency fixes to the Secrets tab:
1. Console header reads "edit workspace · <name>" (Edit) and
"create workspace" (Create) instead of "edit · <name>" / "new
workspace" — the missing "workspace" suffix was ambiguous.
2. Empty Secrets tab no longer renders "Workspace env" label or
"(no env vars)" placeholder — both add no information and aren't
present on Mounts/Agents tabs. The empty state is now just the
"+ Add environment variable" sentinel.
3. User-facing TUI text uses "environment" instead of "env" — the
abbreviation makes sense for CLI subcommand names but reads as
noise in the TUI. Sentinel text, modal titles, and footer hints
updated. CLI verbs and Rust identifiers (EnvScope, set_env_var,
WorkspaceConfig.env) are unchanged — the TUI is the only surface
where the full word matters.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): picker loading panel shows breadcrumb so operator knows what stage is loading
The picker title was emitting a full breadcrumb only after the load
completed, so during the 1-3s op subprocess the modal said just
"1Password" with no context — operator couldn't tell whether they were
waiting for vaults, items, or fields. Stage transitions advance at
request-time now (not result-time), so the title reflects the target of
the in-flight load: `<email> → <vault>` while items load, `<email> →
<vault> → <item>` while fields load, etc. Loading body messages adapt
the same way: "loading vaults from <email>", "loading items from
<vault>", "loading fields from <item>".
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): Q always opens an exit confirmation when not on the main screen
Pressing Q anywhere outside the main manager list now opens an "Exit
jackin'?" Y/N confirmation dialog. The operator can press N or Esc to
return to exactly where they were — useful when Q is accidentally hit
mid-flow. Q on the main screen still exits silently (today's behavior).
Q in a text input modal (EnvKey, EnvValue, picker filter) is treated as
input and never triggers the confirm. Routing is gated by
is_on_main_screen + consumes_letter_input checks at the top of the event
loop, so the dialog is the single chokepoint.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): field-load title shows parent context, item moved to loading body
While loading an item's fields, the picker title now shows the parent
context (`<email> → <vault>`) and the loading body names the item being
descended into (`loading ChainArgos Redshift (donbeave)…`). Previously
the title carried the item too, which made it ambiguous what was loading
versus already loaded. Principle: title = where you are, body = what
you're descending into. The same rule already applies to vault and item
loads (which were unaffected by this change).
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): block duplicate env keys live in the create-env-name modal
The EnvKey TextInput modal now carries the current scope's existing
environment-variable keys as a "forbidden" list. While the operator
types, an inline red warning shows when the name collides — and Enter
is blocked until the name is changed or cleared. Workspace-scope and
agent-scope sentinels each populate the forbidden list from their own
env map, so a key named "DB_URL" can coexist on workspace and agent
overrides without false positives.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): rename Secrets tab to Environments
The values managed in this tab aren't always secrets (DEBUG=1 is just a
config flag), so "Environments" is the more accurate label. Tab strip,
modal titles, and docs prose updated. Internal identifiers (EditorTab::
Secrets, SecretsRow, secrets_*) are unchanged — they describe the
codepath, not the user-facing label.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): op:// rows render as breadcrumb, never mask, ignore Enter
1Password references are paths, not credentials. Render them as a
breadcrumb (`<email>/<vault>/<item>→<field>` for multi-account,
`<vault>/<item>→<field>` for single-account) styled with dim/white/green
hierarchy so the field name reads as the addressable thing. Drop masking
on op:// rows entirely — the breadcrumb isn't a secret, no point masking
it. Block Enter from opening a text-edit modal on op:// rows; operators
delete and re-add via the source picker for changes. Plain-text rows
unchanged.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): per-row masking — M toggles the focused row only
Replace the global secrets_masked bool with an unmasked_rows BTreeSet
keyed by (scope, key). M toggles membership for the focused row;
everything else stays masked by default. Tab leave clears the set so
re-entering the tab returns to the all-masked baseline. Op:// rows
ignore the set entirely — they render as breadcrumbs (commit 31), not
masked values, so M is a no-op on them.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): mark op:// rows with ⚿ glyph; sort env keys alphabetically
Visual differentiation between plain-text and 1Password environment
variables: a left-edge ⚿ (lock-and-key) glyph appears on op:// rows,
plain rows render two blank spaces in the same column for alignment.
Both row types share one alphabetically-sorted list — no section break
— so the operator can scan keys in order and see the source at a
glance.
BTreeMap iteration already produces sorted keys, so the alphabetical
ordering is a confirmation pass with a regression-pinning test rather
than a code change to the iteration order.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): add per-agent override entry point on Environments tab
Operators can now add overrides for agents that don't yet have any —
previously the override section only appeared once `pending.agents`
already had an entry, so a fresh agent had no UI path to its first
override key.
A new sentinel row at the bottom of the Environments tab — `+ Add
per-agent override for ...` — opens an agent picker filtered to allowed
agents without existing overrides. Picking an agent creates the override
entry, auto-expands the new section, and lands the cursor on the
section's `+ Add <agent> environment variable` sentinel so the operator
can immediately add the first key. The sentinel hides itself when no
eligible agents remain.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): per-agent override picker drops into normal add flow
The picker for per-agent overrides was creating an empty section with a
"(0 vars)" header and a redundant per-agent "+ Add" sentinel inside as
soon as the operator chose an agent — looking messy if the operator
canceled out, and visually inconsistent with how workspace-level entries
are added. Now picking an agent opens the EnvKey modal directly with
Agent scope, and the rest of the chain (SourcePicker → value) is the
same as the workspace-level path. The agent's section materializes
organically when the first key/value commits; canceling at any modal
step leaves pending.agents untouched, so no orphan sections appear.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): TextInput shows red border on duplicate, hides Enter hint when invalid
Three small polish tweaks to the EnvKey TextInput modal so the visual
state always matches what Enter will actually do:
- Duplicate value → red border (DANGER_RED), inline ⚠ warning, footer
hint shows only `Esc cancel`.
- Empty value → default border (no error styling — empty isn't "wrong",
it's "not ready"), no inline warning, footer hint shows only
`Esc cancel`.
- Valid value → default border, footer hint shows `Enter confirm ·
Esc cancel`.
Enter blocks on both empty and duplicate. The widget gains an
`is_valid()` predicate that gates both the commit and the hint.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): scope-first add flow — pick All agents / Specific agent at start
Replaces the bottom-of-tab "+ Add per-agent override for ..." sentinel
landed in commit 34 with a scope question at the start of the add
flow. Press Enter on the workspace-level "+ Add environment variable"
sentinel; the new ScopePicker modal asks "All agents" or "Specific
agent". All-agents drops into the workspace add flow as before.
Specific-agent opens the agent picker (now showing every allowed agent
— no longer filtered by "doesn't yet have overrides", since operators
legitimately want to add more keys to agents that already have some),
then the EnvKey modal with Agent scope. The fast-path
"+ Add <agent> environment variable" inside an expanded agent section
is unchanged.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): AgentPicker layout follows the canonical list-modal pattern
The AgentPicker drifted stylistically from the OpPicker — different
title format (filter inlined), missing persistent filter row, "no
matches" placeholder, hard-coded "Enter launch" footer text. Operators
saw two visually distinct pickers for the same conceptual "pick from a
list with filter-as-you-type" interaction.
Refactor to match the OpPicker's layout: persistent Filter: row at
top, list body below, standardized footer. AgentPicker gains a
configurable confirm_label so the Enter-action word reads "launch" in
the launch-disambiguation path and "select" in the override-scope path
— same widget, two contexts, no truncation or wrong wording.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(rules): document the canonical TUI list-modal layout
Codify the list-modal layout pattern (filter row, list body, footer)
that the OpPicker, SourcePicker, and AgentPicker now share. Future
picker widgets follow the same shape so the TUI stays visually
consistent across modals. Sits alongside the existing TUI Keybindings
rule in RULES.md.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): Environments tab layout — blank-line between sections, [op] marker
Three small layout fixes for the Environments tab:
1. Add a blank-line spacer between the workspace section and each
agent override section. Cursor skips spacers on ↑/↓.
2. Replace the ⚿ glyph marker on op:// rows with a clearer `[op]` text
marker. Better terminal compat and immediately legible as "1Password".
3. Standardize the row prefix at 7 characters (2-char cursor column +
5-char marker column) so plain and op:// rows align across the tab.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): agent-section headers absorb ←/→ regardless of expanded state
Pressing → on an already-expanded section header (or ← on a collapsed
one) was falling through to tab cycling — operators saw the active
editor tab change unexpectedly when arrowing on `▼ Agent: <name>` or
`▶ Agent: <name>` rows. The header now absorbs ←/→ in both states:
acts on the row when relevant, no-ops otherwise, but never lets the
keypress reach the tab handler.
Codifies the convention as "Contextual key absorption" in
RULES.md § TUI Keybindings so future row types follow the same default.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): show environments on the main screen — workspace + per-agent
The main-screen workspace-manager preview gains a read-only
Environments block listing the highlighted workspace's env-variable
keys, sorted alphabetically with `[op]` markers on op:// references.
The Agents block now lists each agent's env overrides nested under the
agent name, with a "(no overrides)" placeholder for agents that don't
have any. Block order is General → Mounts → Environments → Agents so
the operator scans config-shaped data first and agent-specific things
last.
The preview stays read-only — `E` enters the editor for changes. Only
key names appear; values (plain or op://) never render.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): consolidate env preview — workspace + per-agent in one block
The main-screen Environments block now holds all env-related content:
workspace-level keys under an "All agents:" sub-header first, then each
agent's overrides nested under per-agent sub-headers. Agents without
overrides are omitted from this block — their absence is the signal.
The Agents block reverts to a lean summary (default + allowed agent
list) with no env nesting; per-agent env detail lives only in the
Environments block now.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): main-screen env preview is one flat alphabetical list with right-side agent label
Replace the workspace+per-agent sub-section layout with a flat,
alphabetical list. Each row shows the [op] marker, the env key name,
and (when scoped to a specific agent) the agent name on the right.
Workspace-level rows have an empty right column. Same env name
appearing at both workspace and agent level renders as two distinct
rows; sort puts workspace rows before agent rows when names tie.
The Agents block stays lean (default + allowed-list, no env nesting).
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): omit Environments preview block when workspace has no env vars
The main-screen Environments preview block was rendering an empty
"(no environment variables)" placeholder for workspaces with zero env
entries — visually noisy and unhelpful when most workspaces don't have
any. Now the block is omitted entirely when both workspace-level env
and per-agent overrides are empty; the Agents block fills the freed
space. Block appears as soon as any env entry exists at any scope.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): add 1-cell right padding on Environments preview agent label
Agent labels on per-agent env rows were rendering flush with the right
border. Subtract one cell from the right-padding budget so the label
ends one column before the border, matching the visual breathing room
of other right-aligned content.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): TextInput modal — 1-cell padding + subtle dim background on input field
Two small visual polish tweaks to the TextInput modal:
1. Input text now has a 1-cell left and right padding so it doesn't
sit flush against the modal border.
2. The input field gets a very subtle dim background tint
(Color::Rgb(20, 24, 22)) so the input region is visually distinct
even when empty — hinting "this is where you type".
Applies to every TextInput-modal use across the TUI (EnvKey, EnvValue,
workspace name, mount paths, etc.) since they all share TextInputState.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): TextInput dim background respects 1-cell padding on each side
Commit 47 painted the dim INPUT_BG_DIM background across the full input
row, including the 1-cell pads on each side. The pads should stay the
panel color so the dim band reads as the input region, not the whole
row. Move the bg block from `input_row` (full inner width) to
`textarea_area` (inset by 1 each side); the band now spans only where
input actually lives.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(op): strip trailing newline from `op read` output
`op read` appends a trailing newline as CLI convention. The resolved
env value was carrying that newline through to the container — when the
shell exported the var, `env` showed a blank line after the value.
Strip exactly one trailing `\n` (and an optional preceding `\r` for
Windows line endings) so internal newlines in legitimately-multi-line
secrets survive while the CLI's terminal newline is removed.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(reviews): land Claude and Codex detailed reviews of PR #171
Two independent reviews of the workspace-manager-tui PR's 50-commit
scope, captured side-by-side under docs/superpowers/reviews/. Used to
guide the final pre-merge sweep — keeping them tracked so the audit
trail of what was checked, by whom, and what was flagged stays visible
in git history alongside the work itself.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(config): validate candidate before rename + reject reserved/unknown-agent in env setters
ConfigEditor::save was renaming the temp file over the real config
BEFORE running the validation that AppConfig::load_or_init performs.
Two operator-reproducible bricking cases:
1. `jackin config env set FOO bar --agent ghost` wrote `[agents.ghost]`
without the required `git` field. Subsequent CLI commands failed at
load with "missing field `git`" before they could unset the value.
2. `jackin config env set DOCKER_HOST tcp://bad` succeeded silently,
then ALL future `config env *` commands failed at load with the
reserved-runtime-name validation.
Two-layer fix:
- Setter-level pre-flight: reject reserved env names (DOCKER_HOST and
the rest of the runtime list) and unknown agents at set_env_var
time, before any document mutation. Faster failure, clearer error.
- Save-level safety net: parse the candidate file via the same
validation load uses, BEFORE renaming over the real config. If
validation fails, remove the temp file and propagate the error;
the real config stays untouched.
Adds five integration tests covering the two reproduction paths plus
the save-level rollback invariant.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(console): refresh workspace list after manager save so launch routing isn't stale
ConsoleState.workspaces was a snapshot captured at console startup and
never refreshed. Manager edits (create/rename/edit/delete) rebuilt
ManagerState.workspaces but the launch dispatcher kept reading the
stale snapshot — so a renamed workspace appeared correctly in the list
but failed to resolve at launch, and edits to default_agent /
allowed_agents were ignored by launch routing until the next console
restart.
Chose Option B (derive launch choices from current AppConfig per-call)
because the call sites were tractable: only `dispatch_launch_for_workspace`
and the `LaunchWithAgent` arm in `run_console` consumed the snapshot,
and `WorkspaceChoice` is fully derivable from `(name, &AppConfig, &cwd)`.
Option B eliminates the duplication outright rather than papering over
it with refresh-after-save plumbing — there is no second source of truth
to keep in sync. `ConsoleState.workspaces: Vec<WorkspaceChoice>` and
`selected_workspace: usize` are dropped; a new
`pending_launch: Option<LoadWorkspaceInput>` pins the picker target
across the AgentPicker open→commit gap. `build_workspace_choice` is
exported from `console::state` and called per dispatch from current
config.
Adds four integration regression tests in tests/manager_flow.rs covering
create/rename/edit-default_agent/delete paths, plus two unit tests on
`build_workspace_choice` itself in src/console/state.rs (returns None for
unknown saved name; picks up default_agent from current config).
Co-authored-by: Claude <noreply@anthropic.com>
* fix(op): use op-provided `reference` field; parse 4-segment as vault/item/section/field
Two correlated correctness fixes for 1Password reference handling:
1. Use the `reference` field that `op item get --format json` returns
instead of synthesizing `op://Vault/Item/Field` from display names.
The synthesized form was wrong for fields living inside sections,
for items whose names contained `/` or whitespace, and for any case
where 1Password's authoritative serializer differs from naive
display-name concatenation. The new path always uses the string `op`
itself produces — no synthesis, no escaping bugs.
2. Fix `parse_op_reference` to interpret 4-segment references per the
official syntax (https://developer.1password.com/docs/cli/secret-reference-syntax/):
- 3 segments: `op://<vault>/<item>/<field>`
- 4 segments: `op://<vault>/<item>/<section>/<field>`
The previous "4 segments = account/vault/item/field" interpretation
was a jackin-specific extension that conflicted with op CLI itself.
Account scope stays separate state on the picker (`selected_account`
in OpPickerState) and is not encoded in the path.
The trust model is preserved: `RawOpField` continues to omit the
`value` field, and the safety test exhaustively destructures `OpField`
to fail compilation if anyone ever adds it. The destructure now also
covers the new `reference` field.
Cross-account references where the chosen vault lives outside the
op CLI's default account remain a documented limitation for this PR
— operator must ensure the relevant account is signed in.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(op): timeout op CLI probe; async account_list in picker constructor
OpCli::probe was using plain Command::output() with no timeout — a
wedged op (network stall, biometric held open, etc.) would freeze the
caller indefinitely. Route probe through the same channel-and-thread
timeout pattern that read/vault_list/item_list/item_get use; extract
spawn_op_with_timeout so probe (no JSON parse) and run_op_json (JSON
parse) share the spawn-and-wait primitive.
The picker constructor was calling account_list synchronously, which
blocked the TUI render loop before the spinner could paint. Move the
initial probe onto a worker thread (sharing a single Arc<dyn
OpStructRunner + Send + Sync> with the picker so test stubs are
captured instead of replaced by a fresh OpCli); the picker renders
the spinner immediately, account_list completes asynchronously, and
poll_load routes 0/1/multi to the right state via a new
handle_accounts_loaded helper.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(op): truncate_stderr respects UTF-8 char boundaries
truncate_stderr was slicing a String at byte index 4096. If that byte
fell inside a multi-byte UTF-8 character (e.g., op CLI emitting an
error in a non-ASCII locale, or any future op output containing
multi-byte glyphs), the slice would panic on an error path. Round the
truncation point down to the nearest char boundary at or before MAX.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): EnvValue modal allows empty values; target-specific input validity
TextInputState::is_valid was globally rejecting empty input, which is
correct for EnvKey/Name/Workdir but wrong for EnvValue — POSIX env
semantics distinguish VAR="" (set to empty) from unset VAR, and some
workloads use empty values to clear inherited defaults.
Add an allow_empty flag on TextInputState (settable via
new_allow_empty constructor); is_valid honors it for the empty-string
case while still rejecting duplicates. Wire the EnvValue modal sites
to construct via new_allow_empty. Other text-input targets keep the
non-empty default.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(tui): SourcePicker cycle dedup + align Secrets letter modifier guards
Two small consistency tweaks called out by review:
1. SourcePickerState had byte-identical cycle_forward and cycle_backward
methods (a 2-button toggle has only one transition). Collapse to a
single cycle() matching ScopePickerState's pattern.
2. The Secrets-tab letter shortcuts were using two different modifier
guards: m|M and p|P checked `(modifiers - SHIFT).is_empty()` (the
convention codified in RULES.md § TUI Keybindings), while d|D and a|A
checked `!modifiers.contains(CONTROL)` (accepted Alt/Cmd/Super
silently). Align d|D and a|A with the canonical pattern so all four
letter shortcuts behave the same way.
Both items are pure refactors — no operator-visible behavior change.
Co-authored-by: Claude <noreply@anthropic.com>
* test(op): picker async workers exercise the injected runner
Commit 55 switched the picker's runner field from Box<dyn OpStructRunner +
Send> to Arc<dyn OpStructRunner + Send + Sync> so worker threads share
the same runner instance the constructor was given. That removed the
test-vs-production gap where injected mocks were silently bypassed by
spawned threads (which were building fresh OpCli instances).
Land the regression tests that exercise this newly-reachable surface:
vault_list, item_list, and item_get all now pass through the injected
mock when called from worker threads. Future changes to the picker's
async loading paths can be caught by tests instead of only at runtime.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(reviews): mark PR #171 review findings as resolved
All findings in `pr-171-claude-detailed-review.md` and
`pr-171-codex-detailed-review.md` were addressed by commits 52–59
(workspace-list refresh, candidate-config validation, op://
reference fidelity, EnvValue empty-OK, async account_list, UTF-8
stderr truncation, async-worker test seam, SourcePicker dedup,
Secrets-tab modifier-guard alignment).
Move each finding to a "Resolved" section with the commit hash that
closed it so the markdown reflects current branch state and future
review passes don't re-flag closed concerns.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(op-picker): refresh stale comments about runner threading
Several comments in `src/console/widgets/op_picker/mod.rs` still
described the pre-commit-55 contract where worker threads built a
fresh `OpCli` regardless of the injected runner. That changed when
`OpPickerState::runner` switched from `Box<dyn OpStructRunner +
Send>` to `Arc<dyn OpStructRunner + Send + Sync>` and
`runner_clone_for_thread` became a thin `Arc::clone`. Reword:
- `OpPickerState::new` doc — "Boxes a fresh OpCli" → "Wraps a fresh
OpCli runner in an Arc". The constructor wraps in `Arc::new`, not
`Box::new`, and the wrapping is what enables the worker-thread
share.
- `enter_on_account_advances_to_vault_with_account_scope` test doc —
drop the "spawned thread runs through `runner_clone_for_thread`"
line and the parenthetical claim that the spawned thread builds a
fresh OpCli. Replace with an explicit pointer to the
`vault_list_uses_injected_runner_in_async_worker` test that
exercises the worker-thread path through the injected stub.
- "OpCache integration tests" header comment — drop the "(always
OpCli)" phrasing and reframe the section as "tests that
short-circuit before the worker spawn", which is what they
actually do today.
No code or behavior change.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(tui): consolidate pending env writes through one helper
Three Secrets-tab commit paths produced a final
`(scope, key, value)` triple and inserted it into `editor.pending`:
- `OpPicker` key-row commit (already routed through a helper);
- `EnvValue` text-modal commit (open-coded match);
- `EnvKey` commit's sentinel-picker fast path (open-coded match).
The two open-coded sites repeated the agent-scope auto-create +
`secrets_expanded.insert(agent.clone())` dance with the same
`pending.env.insert(...)` / `pending.agents.entry(...).or_default()`
shape. A future change to the contract (e.g. tracking dirty rows,
emitting a save-prompt dirty bit, recording last-edited timestamps)
would have to update three places in lockstep.
Rename `apply_picker_value_to_pending` → `set_pending_env_value`
since it's no longer picker-specific, expand the docstring to spell
out all three callers and the auto-expand-section invariant, and
route the two open-coded sites through it. Net `-13 +12` LOC; no
behavior change. All 1046 nextest cases still pass.
Co-authored-by: Claude <noreply@anthropic.com>
* chore(comments): trim verbose docs in operator_env + op_cache
Strip comments that restate the code, narrate commits, or rephrase
function/test names. Keep only WHY-level rationale: trust-model
invariants, external-system quirks, subtle behavior preserved
intentionally.
Co-authored-by: Claude <noreply@anthropic.com>
* chore(comments): trim verbose docs in widgets
Strip comments that restate the code, narrate commits, or rephrase
function/test names. Keep only WHY-level rationale: trust-model
invariants, external-system quirks, subtle behavior preserved
intentionally.
Co-authored-by: Claude <noreply@anthropic.com>
* chore(comments): trim verbose docs in manager state + input
Strip comments that restate the code, narrate commits, or rephrase
function/test names. Keep only WHY-level rationale: trust-model
invariants, external-system quirks, subtle behavior preserved
intentionally.
Co-authored-by: Claude <noreply@anthropic.com>
* chore(comments): trim verbose docs in manager render
Strip comments that restate the code, narrate commits, or rephrase
function/test names. Keep only WHY-level rationale: trust-model
invariants, external-system quirks, subtle behavior preserved
intentionally.
Co-authored-by: Claude <noreply@anthropic.com>
* chore(comments): trim verbose docs in console + config/editor
Strip comments that restate the code, narrate commits, or rephrase
function/test names. Keep only WHY-level rationale: trust-model
invariants, external-system quirks, subtle behavior preserved
intentionally.
Co-authored-by: Claude <noreply@anthropic.com>
* chore(comments): trim verbose docs in tests
Strip comments that restate the code, narrate commits, or rephrase
function/test names. Keep only WHY-level rationale: trust-model
invariants, external-system quirks, subtle behavior preserved
intentionally.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(operator_env): collapse repeated layer loops in validate + merge
`validate_reserved_names` repeated the same `for key in env.keys() { if
is_reserved(key) { offenses.push(...) } }` pattern four times, once per
EnvLayer variant. With the explanatory comments trimmed it was just four
copies of the same shape. Hoist into a local closure that takes the
layer label and the env map; the four call sites now read as a tiny
table of layer constructors.
`merge_layers` likewise had four byte-identical insertion loops; iterate
over the four layer references in priority order instead.
No behavior change. Test count unchanged at 1046.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(operator_env): extract build_attributed_layers helper
`resolve_operator_env_with` and `write_launch_diagnostic` each rebuilt
the same per-layer attributed map: global → agent → workspace →
workspace-agent, later wins. Two ~30-line copies of the same precedence
ladder, with comments now stripped, made the duplication obvious.
Hoist into `build_attributed_layers(config, agent, workspace)`. The
diagnostic site keeps the single extra step it needs (drop keys not in
`resolved`). The resolver site keeps the dispatch loop on top.
A regression in either copy of the ladder would have silently dropped
attribution for a layer; one source means errors and diagnostics are
guaranteed to agree on which layer supplied each key.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(op-picker): hoist Up/Down cursor cycle into a shared helper
Each of the four pane key handlers (Account/Vault/Item/Field) had a
byte-identical Up/Down arm that computed `(cur == 0 ? n-1 : cur-1)` /
`(cur+1 >= n ? 0 : cur+1)`. Eight near-identical bodies — readable in
isolation, painful to keep in sync if the wrap-around rule ever
changes.
Hoist into `OpPickerState::step_selection(&mut ListState, count, delta)`.
Each pane site now reads as one line per direction: compute filtered
count, call the helper.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(workspace): derive Default on WorkspaceConfig; collapse fixtures
`WorkspaceConfig` had every field defaultable (`String`, `Vec`,
`Option`, `BTreeMap`) but no `Default` derive, so 20+ fixtures across
console tests, two production builders, and the editor's `new_create`
spelled out all seven fields by hand. With the trimmed comments off,
the noise from the literals dominated the test bodies' actual intent
(which mounts? which agents? which env?).
Add `#[derive(Default)]`. Rewrite each fixture site to either
`WorkspaceConfig::default()` (when it really wants the empty
workspace) or struct-update syntax (`{ field: ..., ..Default::default() }`)
when it customizes one or two fields. Test bodies are now only as wide
as the field they care about.
Production sites updated to match: `EditorState::new_create` and
`WorkspaceCreate::build_workspace` use `WorkspaceConfig::default()` /
struct-update. No serialization or behavior change — the `Default`
impl produces the same all-empty value that every fixture was
spelling out.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(widgets): unify cursor-cycle helper across all list pickers
`agent_picker`, `github_picker`, `workdir_pick` each carried a
hand-rolled wrap-around cursor body inside their Up/Down arms — six
near-identical clones of the `(cur == 0 ? n-1 : cur-1)` /
`(cur+1 >= n ? 0 : cur+1)` pattern. `op_picker` had its own private
`Self::step_selection` helper covering the same four-pane case.
Hoist the helper to `console::widgets::cycle_select` (pub(crate)), drop
op_picker's private copy, and route all five pickers through it. The
`unwrap_or(0)` vs `map_or(0, …)` difference was non-load-bearing: every
caller already initializes `select(Some(0))` on a non-empty list and the
`count == 0` short-circuit covers the empty case identically.
Net: -45 LOC, single source of truth for wrap semantics, zero
behaviour change.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(workspace): finish WorkspaceConfig fixture sweep across remaining sites
Iteration 2 added `Default` to `WorkspaceConfig` and converted six TUI
test files that dominated the noise. Fifteen further sites still spelled
out the seven-field literal: production helpers (`resolve.rs`,
`planner.rs`, `mod.rs`), config CRUD (`config/mod.rs`,
`config/editor.rs`, `config/workspaces.rs`), state-machine fixtures
(`console/manager/state.rs` + four `input/*.rs` siblings),
operator-env reserved-name validators, app-context targeting, and two
integration test files (`manager_flow.rs`, `workspace_config_crud.rs`).
Each site now uses struct-update (`{ workdir: …, mounts: …,
..Default::default() }`) — keeping only the fields the test actually
cares about. No serialization or behaviour change: `Default` produces
the same all-empty values the literals were spelling out.
Co-authored-by: Claude <noreply@anthropic.com>
* test(manager_flow): collapse seed_config into seed_config_with_env
`seed_config` and `seed_config_with_env` were near-identical except for
one field — `env: BTreeMap::new()` vs `env: env_map`. Now that
`WorkspaceConfig::default()` lands the empty case, `seed_config`
delegates: `seed_config_with_env(paths, temp_dir, vec![])`.
Removes one duplicated workspace-skeleton fixture; both tests-paths
exercise the same code instead of two near-identical copies that could
drift.
Co-authored-by: Claude <noreply@anthropic.com>
* docs(reviews): drop fully-resolved Claude + Codex reviews
All findings from both reviews have been addressed across commits 52–59
and subsequent polish iterations. The full audit trail (file contents,
finding-to-commit mapping, every fix discussion) lives in the git log
itself; the standalone markdown files no longer carry information that
isn't already searchable in commit messages and PR history.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor: replace production unwraps with structural matches
Four `.unwrap()` sites in non-test code each guarded an invariant
already established by the immediately preceding `match` on a
collection's length, but expressed it as `into_iter().next().unwrap()`
or `as_ref().unwrap()`. Rewriting each as a slice-pattern match,
`Option::map_or`, or `swap_remove(0)` carries the invariant in the
type rather than in the prose, so a future refactor that drops the
length guard becomes a compile error rather than a runtime panic.
No behavior change.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(tests): finish WorkspaceConfig fixture sweep in manager_flow
Three remaining `WorkspaceConfig { … last_agent: None, env: …::new(),
agents: …::new() }` literals in manager_flow's helpers and tests still
spelled out the all-default tail by hand. The previous sweep landed
the pattern across the repo but skipped these three because each had
a non-default field interleaved (`allowed_agents`, `default_agent`)
that hid the trailing `..Default::default()` opportunity from a
literal grep. Hoist the trailer everywhere; the fixture intent
becomes "set these specific fields, defaults for the rest" rather
than mechanical zero-elision.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(manager): make manager-private submodules non-pub
`agent_allow`, `create`, and `github_mounts` are referenced only from
inside `console/manager/`. Drop their `pub` to express that boundary
in types — the public surface of the manager module remains `input`,
`render`, `state`, and `mount_info` (the last is reached from the
file-browser git prompt).
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(render): drop dead op_accounts chain + merge is_op double-scan
- Remove `op_accounts: &[OpAccount]` from `render_secrets_key_line`,
`render_secrets_tab`, and `render_editor`; the param was immediately
discarded with `let _ = op_accounts` and carried no information.
- Replace the two-scan pattern (`is_op_reference` then `parse_op_reference`
on the same string) with a single `parse_op_reference` call; use
`.is_some()` to derive the `[op]` marker. Eliminates one `starts_with`
scan per key row per frame.
- Update the two `render_secrets_tab` call sites in tests and the single
`render_editor` call site in `render/mod.rs`.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(nav): eliminate double secrets_flat_rows allocation on cursor move
`step_secrets_cursor_down` and `step_secrets_cursor_up` each called
`secrets_flat_rows` internally, duplicating the allocation in the same
keystroke that also called `secrets_flat_row_count` (another clone of the
same Vec). On every Down keypress in the Secrets tab, two full Vec<SecretsRow>
were allocated and dropped.
Fix: compute `rows` once at the call site, pass `&[SecretsRow]` into the
step helpers. Both helpers lose their `editor`/`config` parameters and the
internal `secrets_flat_rows` call. Remove `secrets_flat_row_count` entirely
(it was just `.len()` of the Vec) and drop it from the import and the now-
unreachable `Secrets` arm of `max_row_for_tab`.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(op-picker): borrow account id for cache writes instead of cloning
Add `selected_account_id_ref() -> Option<&str>` alongside the owned
`selected_account_id() -> Option<String>`. Use the reference form in the
three `poll_load` arms that write to the op cache (vaults, items, fields) —
they only need `&str` for the cache key, not an owned `String`. The owned
form is still used by `start_*_load` callers that move the value into a
spawned thread.
Co-authored-by: Claude <noreply@anthropic.com>
* style: rustfmt pass after render/editor.rs refactor
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): correct misleading footer hints + document op_picker invariant
Footer:
- Remove `M mask/unmask` from AgentHeader and add-sentinel footer arms;
`toggle_focused_row_mask` returns early for both row kinds (`_ => return`),
so advertising the key was a silent lie.
- Remove `M mask/unmask` from the SectionSpacer/None fallback arm (cursor
never lands there). Degrade to empty hint.
- Suppress "Enter pick working directory" hint on General tab row 1 when
`pending.mounts.is_empty()`; `open_editor_field_modal` silently does
nothing when there are no mounts to pick from.
- Remove dead `enumerate()` + `let _ = i;` in `footer_spans`.
OpPicker:
- Document the `self.accounts.len() > 1` multi-account invariant in
`handle_accounts_loaded` and the `handle_vault_key` Esc guard; the single-
account fast-path intentionally leaves `self.accounts` empty, making
the len check the canonical multi-account signal.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(render): thread live config into current-dir details pane
`render_current_dir_details_pane` was constructing `AppConfig::default()`
for both the agents-block height calculation and the `render_agents_subpanel`
call, so the Agents sub-panel on the "Current directory" row always rendered
zero agents regardless of what was actually configured.
Fix: add `config: &AppConfig` parameter and thread it in from the call site
in `render_list_body` (which already had `config`).
Also: document the `TempDir` drop requirement in `seed_config_with_env` so
a future test can't accidentally drop the guard before assertions complete.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(tui): cap startup op probe at 3s; gate P key + hint on op_available
Startup probe:
- Add `OpCli::new_probe()` with a 3-second timeout. `OpCli::new()` retains
the 30-second `OP_DEFAULT_TIMEOUT` for all operational calls (vault-list,
item-get, etc.) where blocking is expected. The startup probe only needs
to know whether `op` is installed and responsive; a false negative here is
acceptable — the picker renders its own error state on first open.
- Use `new_probe()` in `ConsoleState::new` so a network-stalled or
biometric-blocked `op` binary cannot freeze `jackin console` startup for
up to 30 seconds before the terminal is painted.
P key guard:
- Add `&& op_available` to the `P` arm in `handle_editor_key`; when `op`
is absent the key silently does nothing rather than opening a picker that
immediately shows a fatal error panel.
- Thread `op_available: bool` through `render_editor` → `contextual_row_items`
and conditionally omit the `P · 1Password` footer items on key rows and
add-sentinel rows when `op` is unavailable.
- Set `state.op_available = true` in the five `op_picker_*` integration tests
that exercise the `P` key path directly.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(confirm-save): off-by-one in required_height clips last diff line
The layout comment correctly counted 7 chrome rows (2 borders + top blank +
after-content blank + buttons + after-buttons blank + hint), but the
implementation returned N + 6. The modal was sized 1 row too short,
clipping the last changed-field line in every ConfirmSave dialog.
Fix: return N + 7, matching the documented layout. Update the test
assertion from 9 to 10 for a 3-line state.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): select just-saved workspace on return to list
After saving a workspace via the Esc → Save path (or after creating a new
one), the list cursor now lands on the workspace that was just written rather
than defaulting to the CWD row or the first entry.
The final on-disk name (`current_name`) is already correct in all cases —
the rename path updates it before the edit_workspace call — so it can be
looked up in the freshly-rebuilt ManagerState and used to set `selected`.
Adds a regression test with two workspaces to verify the cursor targets the
right entry, not just "index 1" by accident.
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(confirm-save): move Create-mode env section into its match arm
The env-vars block for EditorMode::Create was appended after the match via
a second `if matches!(...Create)` guard, while the Edit arm correctly handled
its env section inline. This split made the Create arm appear complete when
it wasn't, risking duplication if someone added env logic inside the arm
without seeing the post-match block.
Move the env section into the Create arm directly, after the default-agent
block. Behaviour is identical; the structure now matches the Edit arm.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(op-picker): clear selected_item on Field→Item Esc back-navigation
`handle_item_key` Esc (Item → Vault) clears `selected_item = None`.
`handle_field_key` Esc (Field → Item) did not, leaving a stale item
reference on `self` while the operator was back on the Item pane.
The stale reference caused the breadcrumb to show the previous item name
during the first render frame after back-navigation. Add the clear to
match the established Esc-back pattern.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(tui): s always exits to workspace list after successful save
Previously `s` used exit_on_success = false — it saved but kept the
operator inside the editor. The Esc → Save path already used
exit_on_success = true (returning to the list). This made `s` the odd
one out: the operator had to press Esc a second time to get back.
Change `s` to also use exit_on_success = true. Both save paths now
return to the workspace list after a successful write, with the cursor
positioned on the just-saved workspace (behaviour from an earlier commit).
Update two tests that were asserting the old "stays in editor" behaviour.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(workdir-pick): scroll list so selected row stays visible
The picker rendered all choices as a Paragraph, which clips without
scrolling. On a deep directory tree (e.g. /home/user/Projects/repo
producing 4-6 ancestor choices) the highlighted row could disappear
off the bottom of the modal with no way to see it.
Apply an offset so the selected row is always within the visible
viewport: offset = selected.saturating_sub(visible - 1). Chain skip()
+ take() directly into the line-building iterator to avoid an
intermediate Vec allocation (clippy: avoid collect when not needed).
Co-authored-by: Claude <noreply@anthropic.com>
* refactor: drop unused _config from secrets_flat_rows and callers
`secrets_flat_rows` took a `_config: &AppConfig` parameter it never used —
the function derives the row model entirely from `editor.pending` and
`editor.secrets_expanded`. Removing it cascades cleanly through six helper
functions that passed `config` solely to forward it here:
- `toggle_focused_row_mask`
- `open_secrets_enter_modal`
- `open_secrets_delete_confirm`
- `open_secrets_add_modal`
- `open_secrets_picker_modal`
- `contextual_row_items`
Also:
- `scope_label` now returns `&str` (avoids two pointless allocations;
clippy promotes it to `const fn`).
- Test `secrets_flat_rows_sequence_is_canonical` pins the exact row
order for a workspace with env keys, an expanded agent, and a collapsed
agent — cursor arithmetic in the input layer is derived directly from
this sequence.
Co-authored-by: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com>
Co-authored-by: Codex <codex@openai.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
* docs(roadmap): iteration 1 — readability & modernization analysis Full first draft of READABILITY_AND_MODERNIZATION.md covering §0–§10: project inventory (72 files, 40k LOC), concept-to-location index (25 entries), documentation hierarchy proposal, source-code structural diagnosis (single-crate recommendation, 7 module-shape rules, hot-spot list), naming candidates, CI/tooling analysis, modernization candidates (13 entries with alternatives comparisons), AI-agent workflow (§8.1–§8.3: spec-driven development, superpowers alternatives, docs boundary), risks, and execution sequencing. All claims grounded in direct code reading (cited file paths and line ranges). All crate/tool recommendations include 2–3 alternatives. PR #171 branch read for AgentPicker, OpStructRunner, RawOpField, and RULES.md additions. Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 2 — launch.rs deep analysis, TS strictness, preview workflow Four concrete improvements over iteration 1: - §4: Deep-read src/runtime/launch.rs (2368L) in full; mapped every function to exact line ranges and dependency graph; refined split proposal into 4 files (launch.rs ~120L, launch_pipeline.rs ~560L prod + 1200L tests, terminfo.rs ~110L, trust.rs ~60L); key finding: test module (1282L) exceeds production code (1083L). - §7.11: Discovered docs/AGENTS.md documents both TypeScript strictness blockers explicitly (rainEngine indexed access + astro-og-canvas optional-property types); verified rainEngine.ts at 5 line locations; rewrote recommendation as a concrete 4-step fix plan. - §6: Read preview.yml in full — Homebrew tap rolling-preview pipeline; flagged missing contributor documentation; resolved OQ3. - §2 concepts 4 & 6: Exact PR #171 data — TICK_MS=50 at console/mod.rs:90 with 20Hz rationale; compile-time safety test is an exhaustive struct destructure (not trybuild) at operator_env.rs:2055–2096. Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 3 — operator_env.rs deep analysis, snapshot tests, OQ7 Three concrete improvements over iteration 2: - §4: Deep-read src/operator_env.rs (1569L); mapped every function to exact line ranges; identified two distinct clusters (op CLI subprocess layer lines 96-364 vs env layer resolution lines 365-808) with ~90L connective tissue; proposed module-directory split into mod.rs (~100L), client.rs (~280L), layers.rs (~470L), picker.rs (~250L PR#171); dependency graph shows no circular imports. - §7.5: Named first 3 concrete snapshot test targets with file:line citations — render_sentinel_description_pane (list.rs:306, zero state, ~10L), render_tab_strip (editor.rs:180, 4 enum variants, ~20L), render_mounts_subpanel (list.rs:408, 3 data-driven cases, ~30L). - OQ7 resolved: docs/package.json confirms astro-og-canvas ^0.11.1; docs/src/pages/og/[...slug].png.ts:~35 has `logo: undefined` which is the exact exactOptionalPropertyTypes conflict; fix is one line (omit the property). §7.11 and §9 updated accordingly. Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 4 — config/editor.rs deep analysis, exact rustdoc coverage Two concrete improvements: - §4: Deep-read config/editor.rs (1467L); key finding: production code is only 503L (lines 1-503), tests are 963L (lines 504-1467 — nearly 2x); mapped all 18 public methods with exact line ranges into 5 domain groups; flagged create_workspace/edit_workspace validation-first architectural pattern as a refactor-preserve invariant; proposed 6-file module-directory split; priority note: lower than launch.rs/operator_env.rs since production code is already a reasonable size. - §1/§4/§7.6: Corrected rustdoc coverage from estimate "~28%" to exact count 37/90 files = 41%; identified cluster: console/manager/ and console/widgets/ (PR #171 additions) are well-covered; runtime/, app/, cli/ lag; updated all three occurrences consistently. Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 5 — concrete brainstorm template, §10 step 4 ordering, §5 pruning Three concrete improvements: - §8.2: Added 17-line concrete docs/internal/agent-skills/brainstorm.md template with 6 fields (Purpose/When/Steps/Outputs/Done when/Overlap guard). The Done-when and Overlap-guard fields are the discipline gates missing from superpowers default brainstorming. Makes §8.2 immediately actionable instead of aspirational. - §10 step 4: Refined split ordering from sketch to priority-grounded sequence: config/types (4a) → manifest (4b) → config/editor 503L (4c) → operator_env 810L (4d) → app/dispatch (4e) → runtime/launch 1083L (4f last). Each sub-step has architectural notes and what-could-go-wrong. Flags the operator_env circular-dependency check for layers.rs→client.rs. - §5: Replaced 3 "leave as is" non-candidates (rows 10, 12, 15) with verified candidates from code reading: provision_claude_auth→apply_auth_forward (auth.rs:17), AuthProvisionOutcome→AuthForwardOutcome (instance/mod.rs), spawn_wait_thread→spawn_exit_watcher (operator_env.rs:202). - OQ5 resolved: instance/auth.rs read — only 210L production code, 585L tests (3x); no split needed; removed from hot-spot concern. Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 6 — hot-spot splits, §8 cc-sdd pivot, op_cache, logging Four improvements: - §1: Hot-spot table rebuilt with production/test LOC split columns for all 22 files. Key finding: manifest/validate.rs is 145L prod/816L tests (exemplary testing, not a god file); app/mod.rs is 928L prod/22L tests (most genuine god file after launch.rs). Adds Priority column. - §8: Operator prefers existing tools over hand-rolled skill files. §8.1 recommendation pivots from hand-rolled (Option C) to cc-sdd (Option B). §8.2 rewrites comparison table — cc-sdd covers spec/plan/execute; TDD/debugging/review covered by existing project docs (TESTING.md, open-review-findings.mdx). Removes custom brainstorm template from iteration 5; no docs/internal/agent-skills/ dir needed. - §2 concept 14: Session-scoped op cache confirmed at src/console/op_cache.rs (252L, all production, no tests). Full detail: BTreeMap keyed by (account, vault_id, item_id) tuples, DEFAULT_ACCOUNT_KEY sentinel, invalidation methods, //! doc confirms metadata-only guarantee. - §7.14: New modernization entry — structured logging (log vs tracing vs current eprintln! approach). Recommendation: defer. Research in _research_notes.md. Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 7 — MDX specs, §9 dep graph fix, §7.13 Renovate full entry Five concrete improvements: - §8.1/§8.3/§3: Operator direction — specs become public Starlight MDX pages at docs/src/content/docs/specs/ (draft: true while in-progress), not in docs/internal/specs/. Living source of truth updated with each code change. §8.3 boundary contract rewritten for public-spec model. §3 target shape updated to remove internal specs/ and add public specs/. - §9 R1: Corrected from "circular import risk" to "compilation-at-distance risk". Verified by grep: config imports workspace (config/mod.rs:1,5,6) but workspace does NOT import config. One-way dep: config → workspace. No circular risk; real risk is 30+ use path updates when AppConfig moves. - §10 step 2: Rewritten to match cc-sdd + Starlight MDX approach (was still describing hand-rolled docs/internal/agent-skills/ from pre- iteration-6 recommendation). Added draft page / lychee link-check caveat. - §7.13 Renovate: Full six-subheading entry replacing 2-sentence stub. Evaluated Dependabot and Renovate Cloud App; both rejected due to DCO sign-off constraint (RENOVATE_GIT_AUTHOR env var in renovate.yml:26 is not replicable in either alternative). Two low-cost tunings named. Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 8 — lychee draft-page constraint, concept 18 correction Three improvements: - §8.1: Draft-page lychee constraint verified from docs/lychee.toml (full read). exclude_path has only 404.html pattern — no draft exclusion. Starlight draft pages exist in dist/ and are scanned by PR-time lychee check. Two fix options added: keep specs link-free, or add exclude pattern to lychee.toml. Added Astro sidebar requirement: manual config at astro.config.ts:50-103; autogenerate{ directory: 'specs' } pattern. - §2 concept 18: Corrected a wrong proposed move. AuthForwardMode → instance/auth.rs was incorrect — the type is used in 9 files, is a config field at config/mod.rs:89,96, and has serde Deserialize at line 74. Moving to instance/ would create circular dep. Corrected to: correctly placed, will move intra-module to config/types.rs in §10 4a. - §4 AuthForwardMode false alarm closed: confirmed NOT a §4 Rule 3 violation. Type is correctly in config because it IS a config value. Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 9 — eprintln count, CODE_TOUR content, step 4a serde note - §7.14: grep-verified 96 production eprintln! calls across 16 files; breakdown by file; no rogue debug calls found, flip condition has not triggered - §2 concept 8: full 4-hop load chain with exact line numbers; documents the /__/ separator invariant in runtime_slug that prevents namespace collisions; CODE_TOUR column now specifies what the tour entry must explain - §10 step 4a: confirms AuthForwardMode serde impl is a plain impl<'de> block (not a derive), moves with the type to config/types.rs automatically Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 10 — dispatch_value scope, Renovate automerge, //! exemplar - §5 row 6: dispatch_value → resolve_env_value rename is 1 prod + 6 test call sites in a single file — lowest-cost rename in the table - §7.13: adds minimal safe automerge pattern for lockFileMaintenance only; explicitly excludes patch/minor bumps and SHA-pinned Actions - §4 Rule 7: expands //! exemplar analysis with direct quotes from env_model.rs:1-17 and isolates the three-element pattern Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): strip iteration annotations; reorder log chronologically READABILITY_AND_MODERNIZATION.md now presents a clean final view: - Remove all "(iteration N)", "(verified iteration N)", "read in full for iteration N", "updated iteration N" qualifiers throughout the body - Remove the Iteration counter from §0 meta - Resolved OQ markers simplified from "(resolved in iteration N)" to "(resolved)"; deferred OQ markers from "Tracked for iteration N" to "Deferred" _iteration_log.md now has entries in chronological order 1–10: - Was: 1, 3, 4, 5, 6, 7, 10, 8, 9, 2 - Now: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): research OpenSpec and IIKit; update §8 landscape _research_notes.md: - OpenSpec (Fission-AI/OpenSpec): brownfield-first SDD, three-phase propose/apply/archive, delta markers (ADDED/MODIFIED/REMOVED), living specs at openspec/specs/; /loop-compatible; recommended as cc-sdd complement for §4 module-split proposals - IIKit (intent-integrity-chain/kit): cryptographic .feature hash-locking, 8-phase workflow, Gherkin BDD chain, Tessl-only install; not recommended for jackin (no Gherkin step runner, Tessl lock-in, heavyweight for single-maintainer) READABILITY_AND_MODERNIZATION.md §8.1: - Adds Options E (OpenSpec) and F (IIKit) to the landscape and evaluation table with brownfield delta tracking and Rust/nextest compatibility rows - Adds OpenSpec complement note after the cc-sdd recommendation Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 11 — dtolnay correction, ci.yml detail, snapshot targets - §7.7 / §2 concept 25 / §10 step 3: correct false claim that dtolnay/rust-toolchain reads rust-toolchain.toml; action version is encoded in SHA rev; three sources require manual sync - §6 ci.yml row: expand from high-level to exact job steps; document gaps (no MSRV job, floating tags in build-validator, no doc job, main jackin binary never compiled in CI) - §7.9: grep-confirm all three snapshot target fn names; add exact signatures and private-fn access pattern (use super::*) with list.rs:720 as existing proof Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 12 — ClassSelector scope, lint table, app split - §5 row 5: ClassSelector → AgentClass rename is 138 prod call sites across 17 files — highest-scope rename in the table - §7.8: full lint table enumeration from Cargo.toml:47-75; notes that cast allowances are global despite TUI-scoped inline comment - §4 4e: app/mod.rs run() read in full; refined split from 1 file to 3 (dispatch.rs ~167L, workspace_cmd.rs ~438L, config_cmd.rs ~220L) Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): remove resolved/no-action items from roadmap Keep only forward-looking actionable proposals: §2 concept table: removed 6 rows with "Stable"/"No change needed" proposals (RULES.md keybindings, hardline, mount planning, XDG paths, Docker builder, env-var ordering); renumbered 1-19 §5 naming table: removed hardline_agent row (leave-as-is, no action); rephrased load_agent row to focus on the //! doc action not the rename; renumbered to 1-14 §9 open questions: removed OQ3 (preview.yml, resolved — in §6), OQ5 (auth.rs split, resolved — no split needed), OQ7 (astro-og-canvas, resolved — fix in §7.11); renumbered to OQ1-4; merged TS strictness OQ back into the active list Cleanup: removed "resolved OQ5" tag from auth.rs hot-spot entry; removed "(resolved OQ7)" qualifier from §7.11 blocker label Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): update analysis baseline — PR #171 now merged to main Remove all "after PR #171 merges", "PR #171 branch", "pre-merge", and "not yet on main" qualifiers throughout. Specific changes: - §0: update baseline note from "treated as merged" to "now merged" - §1: remove branch-only qualifiers from file tree, module map, coverage count - §2: concept rows 1, 2, 4, 5, 6, 10, 11, 12, 14, 15 updated to reflect code now on main; row 12 op_cache current-state rating updated from requires-tribal-knowledge (pre-merge) to requires-grep (post-merge) - OQ1 (op_picker cache): updated from "deferred" to "read from main now" - §4, §5, §7: remove "(PR #171)" and "adds/branch" language - §10 step 1: remove "once PR #171 merges" condition Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Codex <codex@openai.com>
donbeave
added a commit
that referenced
this pull request
May 7, 2026
…183) * docs(roadmap): iteration 13 — AI code verifiability framing, config/types.rs full spec Primary goal shift: codebase must be verifiable for AI-generated code. - §0: replace generic description with explicit verifiability rationale (module contracts, localised concerns, types/behaviour separation) - §4 intro: add "Why structure matters for AI-generated code" section with audit-units table mapping each post-split file to one reviewable question - §4 4a: expand config/types.rs from description to full execution spec — exact type list, post-split mod.rs content, zero-change submodule guarantee (verified: agents.rs/persist.rs/workspaces.rs use super::T which resolves through mod.rs re-exports unchanged), impl-extension pattern already in use documented Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 14 — editor method map, app helpers, //! queue - §4 4c: config/editor.rs split is now execution-ready — complete 6-file method-to-file table with private helper placement verified (validate_candidate→io.rs, table_path_mut→mod.rs pub(super), auth_forward_str→agent_ops.rs, create_workspace delegates to AppConfig) - §4 4e: app/mod.rs split complete — all private helpers mapped (parse_auth_forward_mode_from_cli→config_cmd.rs, workspace_env_scope→workspace_cmd.rs, print_env_table note, remove_data_dir_if_exists→dispatch.rs) - §10 step 5: add //! priority queue — 10 files with draft content, prioritised by cold-landing impact and AI audit risk; selector.rs and instance/mod.rs explicitly document the /→__ invariant Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 15 — dep graph fix, trust.rs safety, OQ1 closed - §4 4d: correct operator_env dependency graph — layers.rs imports both mod.rs (OpRunner) AND client.rs (OpCli for non-injectable resolve_operator_env wrapper at line 797); still a valid DAG - §4 4f: verify trust.rs split safety — FnOnce injection pattern means launch_pipeline.rs has zero dependency on trust.rs; import chain documented; trust bypass audit now requires reading only ~60L - §9 OQ1 closed: op_cache.rs read in full — 4-level structure, per-level invalidation, no TTL/expiry (expiry handled at OpCli subprocess level), DEFAULT_ACCOUNT_KEY sentinel documented Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 16 — CommandRunner Rule 3, render/editor split, 4a/4c independence - Fix duplicate Rule 3 section introduced by previous edit; add docker.rs co-location note as third edge case (three edge cases, not two) - Add render/editor.rs as new Rule 5 violator: 1666L post-PR #171 (was listed as 782L); propose 6-file tab-by-tab split with auditability note on the security-adjacent Secrets tab - Add §10 execution-order note: 4a and 4c are independent — editor.rs imports AppConfig via crate::config re-exports regardless of 4a order - Append iteration 16 log entry with confidence table and weakest sections Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 17 — instance/auth.rs audit, state.rs split, line count corrections - Add instance/auth.rs to //! priority queue at #4: four security invariants (0o600 perms, symlink rejection, TOCTOU-safe writes, macOS Keychain) documented in draft //! content - Add state.rs as new Rule 5 violator: 992L/628L production; 26+ types mixed with impl blocks; propose 5-file types/behavior split - Correct stale line counts: render/list.rs 1122→1989 (PR #171 added render_environments_subpanel); state.rs 865→992; priorities upgraded - Fix §7.9 snapshot function line refs: sentinel_description_pane 306→332, mounts_subpanel 408→433, render_tab_strip 180→269, test ref 720→944 - Renumber //! priority queue to 11 entries (was 10) Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 18 — agent_allow OQ2 closed, render/list.rs split proposal - Close OQ2: agent_allow.rs read in full — 55L, correct //! doc, design sound; serves as model for //! priority queue pattern - Add render/list.rs as new Rule 5 violator: 668L production (PR #171 added render_environments_subpanel); propose 3-file split (mod.rs, details.rs, subpanels.rs); note import-path change for agents_block_agent_count - Update §1 module map: agent_allow.rs entry corrected with size/API Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 19 — input/editor.rs critical correction, split proposal - Correct input/editor.rs: 2349L total (was 1304L), 1141L production (was 547L) — PR #171 added Secrets-tab handlers; pub(super) fn handle_editor_modal at line 618 was invisible to previous grep pattern; now the largest production file in the codebase; priority → Critical - Correct input/save.rs: 1472L total, 661L production (was 567L) - Add 5-file split proposal for input/editor.rs: mod.rs (two dispatchers), secrets.rs (~500L AI-generated Secrets-tab), agents.rs, mounts.rs, general.rs - Update key insight paragraph naming input/editor.rs as largest production file Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 20 — console splits in §10, MSRV evidence, animation.rs verdict - Add console/manager/ as §10 Step 4f group with 5 sub-steps in priority order; rename existing 4f (launch.rs) → 4g; add circular-import risk note for ManagerStage/EditorState split sequencing - Analyze tui/animation.rs: 582L all-production, no split needed (banner_grid is a tightly-coupled rendering loop); section comments compensate for missing //! - Partially close OQ3: u64::is_multiple_of (stabilized 1.86) found in animation.rs; within declared MSRV 1.94; full cargo +1.94.0 check deferred (toolchain unavailable) Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 21 — input/save.rs split, //! queue fix, save.rs corrections - Add input/save.rs split proposal: 4 pub(super) fns discovered; 3-file split (mod.rs + flow.rs + preview.rs); no cross-dependency between flow and preview groups; §10 4f-v updated from Optional to concrete plan - Fix //! queue preamble: "first 10 files" → "first 11 files" - Correct save.rs module map (1418→1472L, correct key exports) and hot-spot table note (begin_editor_save ~280L → ~118L; commit_editor_save is the Phase 2 partner at ~149L) Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 22 — input/list.rs and mount_info.rs analysis - Analyze input/list.rs: 214L production (tests at 215); has //! doc; two focused pub(super) fns; no split needed; Low priority; correct module map - Add mount_info.rs to hot-spot table: 277L production; Low priority; has //! doc; correct module map with 3 public enums + inspect fn - Fix stale §2 diagnosis note: docs/internal/roadmap/ now exists Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 23 — audit units table +5 console rows, input/mod.rs corrected - Expand audit units table from 8 to 13 entries: add state/types.rs, state/editor.rs, input/editor/secrets.rs, render/list/subpanels.rs, input/save/preview.rs — all targeting PR #171 AI-generated console code - Add PR #171 context note linking 5 new entries to AI-generated code concern - Correct input/mod.rs module map: 369L, add InputOutcome enum to exports - Verify rust-toolchain.toml absence; §7.7 and §2 concept 25 already correct Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 24 — render/mod.rs analysis, //! exemplars table, EditorTab confirmed - Add §4 Rule 7 positive exemplars table: 7 files with //! docs graded 1-element (render/mod.rs), 2-element (input/save.rs etc), 3-element (env_model.rs, agent_allow.rs); PR #171 docs-discipline pattern noted - Correct render/mod.rs module map: 421L; FooterItem + palette constants + render_header + centered_rect_fixed added to key exports - Confirm EditorTab variants: General, Mounts, Agents, Secrets (Rust enum) vs "Secrets / Environments" (UI label); /stub qualifier already removed Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 25 — too_many_lines recount, FooterItem PR, MountConfig caveat - Correct too_many_lines count: 13 across 8 → 16 across 11 files (PR #171 added 5 suppressions in console/manager); add full breakdown table; update all 3 occurrences in roadmap - Fix FooterItem PR reference: #165 → #166 (confirmed by git log --follow) - Add MountConfig → MountSpec rename caveat to §7.5 snapshot test description Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 26 — console/mod.rs and op_picker/render.rs analyzed - Add console/mod.rs to hot-spot table: 406L/307L production (Low); correct module map from ~200 → 406L; note missing //! doc with ConsoleStage design block comment worth promoting - Add op_picker/render.rs to hot-spot table: 865L/545L production (Medium); PR #171 AI-generated; 14 functions in two logical groups (entry/helpers vs level renderers); split into levels.rs proposed - Correct 3 stale ~200L estimates for console/mod.rs across roadmap Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 27 — op_picker/mod.rs discovery, render split, operator_env correction - Add op_picker/mod.rs to hot-spot table: 1712L/775L production (High); PR #171 AI-generated; OpPickerState types+behavior split opportunity; has 7-line //! doc; module map split into two rows (mod.rs + render.rs) - Add op_picker/render.rs 2-file split proposal: render.rs (coordinator) + render_pane.rs (pane/level renderers); no cross-dependency confirmed - Correct operator_env.rs total: 1569→2130L (880L production); update 4 occurrences across hot-spot table, ASCII tree, §4 analysis Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 28 — op_picker/mod.rs 3-file split, count corrections - Add op_picker/mod.rs formal 3-file split: loading.rs (async load family ~120L) + keys.rs (4 level key handlers ~315L) + mod.rs (types/constructors) - Correct "24 files" → "28+" for 500L threshold count - Update total LOC: ~40,664 → ~43,587 (2 occurrences, with provenance note) Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 29 — op_picker execution order + file_browser analysis - §10 Step 4f: expand from 5 to 7 sub-steps; add 4f-vi (op_picker/mod.rs → mod.rs + loading.rs + keys.rs) and 4f-vii (op_picker/render.rs → render.rs + pane.rs); document impl-extension and import-path caveats - §4 //! exemplars: add file_browser/ subsystem analysis — all 5 files have //! docs, no file exceeds ~350L production; classified as exemplar (not a split candidate); document git_prompt.rs coupling-density justification and input.rs as 28-file false positive (144L production) - §1 module map: expand single file_browser/ row to 5 individual rows with production LOC and dominant concern per file Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 30 — challenge split-first thesis, fresh LOC corrections - §4: Add "Alternative thesis: documentation-first verification" — challenges the two core assumptions behind file splitting (files-as-audit-unit and file-size-as-context-constraint); adds 7-criterion comparison table vs structure-first approach; introduces phased combined recommendation: Phase 1 = doc sprint (//! contracts + specs/ for 3 subsystems, 2-3 PRs, zero structural change); Phase 2 = splits only for >600L production files (reduces scope from 14+ to 4 files); Phase 3 = workspace if LOC > 150K - Fix stale LOC: app/mod.rs 951→979, config/editor.rs 1467→1548 (7 and 8 locations respectively; verified by fresh find|xargs wc -l scan) - §1 module map: add agent_picker.rs (436L), scope_picker.rs (201L), source_picker.rs (244L) — all PR #171 additions with //! docs Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 31 — fix 600L→800L threshold error, correct LOC - §4 alternative thesis: correct ">600L production → 4 files" claim introduced in iteration 30; re-verified all 9 candidate files via #[cfg(test)] line position; threshold must be >800L to get exactly 4 files (9 exceed 600L); add verification table with test-start lines - Production LOC corrections (5+ locations each): launch.rs 1085→~1077, operator_env.rs 810→~880, app/mod.rs 928→~957, config/editor.rs 503→~584 - §2 OpPicker row: replace vague "no entry yet" with confirmed gap: PROJECT_STRUCTURE.md line 53 still lists pre-PR#171 widget set (10 named); omits op_picker/, agent_picker.rs, scope_picker.rs, source_picker.rs and pre-dates the manager/ sub-structure split Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 32 — two-tier spec arch, behavioral spec template - §8.1: Add two-tier spec architecture table distinguishing feature specs (public Starlight MDX, user-facing) from behavioral specs (internal docs/internal/specs/, for AI code verification) — resolves contradiction between §4 (which said docs/internal/specs/) and §8.1 (which said "no longer needed; specs are public") - §8.1: Add concrete behavioral spec template for op_picker/ with state machine table and 3 INV invariant entries each with a grep-executable "Verify by:" command; template directly usable for the 3 Phase 1 specs - §8.1: Remove erroneous "docs/internal/specs/ no longer needed" claim - Confirmed render/editor.rs ~736L and render/list.rs ~668L production (no interspersed production code — all test blocks follow consecutively) Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 33 — executive summary, §0 correctness - §0: Add executive summary (~300 words) with core problem, 3-phase recommendation, key counter-argument, and navigation table pointing to §2/§4/§7/§8/§10 by question — resolves the meta-irony of a readability roadmap with no entry-point orientation - §0 item 2: "1569-line monolith" → "2130-line monolith" (operator_env.rs current verified size; stale reference was in the first section readers see) - §0 item 3: Add "(selective)" qualifier and explicit note that standard Rust co-locates struct+impl — impl-extension pattern is justified only for files >800L production, not as a universal rule Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 34 — spec priority reorder, §10 Phase 1 track - §0 + §4 Phase 1: Prioritize runtime/launch.rs behavioral spec (no //! doc, ~1077L production, critical path — all jackin load failures trace here); drop config/editor.rs from Phase 1 (its 963L test suite already serves as behavioral spec — tests are behavioral examples); reduce Phase 1 from 3 specs to 2 specs; add reasoning for the priority ordering - §10 Step 2: Split into two parallel tracks — Track A (cc-sdd tooling setup) + Track B (Phase 1 behavioral spec authoring); Track B includes specific INV invariants to capture for runtime/launch.rs grounded in reading the actual function structure (step comment positions); adds sequencing rationale: spec must precede structural splits Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 35 — verified INV entries for runtime/launch.rs Read load_agent_with lines 553-892 in full. Replaced 3 draft INVs from iteration 34 (inferred from step comment positions) with 5 verified INVs citing exact line numbers: - INV-1: trust gate (line 594) precedes image build (line 736) - INV-2: container name claimed (line 754) between image build and network - INV-3: token verified (line 763) before network creation (line 827) - INV-4: render_exit called at lines 886 AND 890 (all exit paths) - INV-5: cleanup disarm semantics — Running→disarm, clean exit→cleanup, crash→disarm (explains jackin hardline compatibility) Corrected wrong line number: claim_container_name call is at 754, not 918 (918 is the function definition). Each INV has a grep-executable Verify by. Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 36 — CI gate for PROJECT_STRUCTURE.md freshness §3: Add "Preventing future PROJECT_STRUCTURE.md staleness" subsection with three concrete options: - Option A: CONTRIBUTING.md rule (necessary but insufficient) - Option B: ci.yml git-diff-scoped shell check (recommended) — only checks files added in the current PR so it doesn't require fixing existing stale entries before merging; greps for module directory name in prose - Option C: Structured TOML module registry (over-engineered for scale) Includes concrete YAML snippet for Option B grounded in the check:repo-links.ts pattern already established in docs/scripts/ Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iterations 36-37 — CI gate + greenfield workspace architecture Iteration 36: - §3: Add "Preventing future PROJECT_STRUCTURE.md staleness" subsection with 3 options (CONTRIBUTING.md rule / ci.yml git-diff check / TOML registry); recommend Option B (git-diff-scoped YAML step) with concrete snippet grounded in existing check:repo-links.ts pattern from docs/scripts/ Iteration 37 (operator directive: greenfield Rust structure): - §4: Add "Greenfield architecture — ideal structure for a growing project" section based on verified cross-module dependency graph (grep iteration 37) - Confirms dependency tiers: workspace/manifest/docker/paths/selector = Tier 0; config/tui/instance = Tier 1; operator_env/runtime/repo = Tier 2; console = Tier 3 - Key finding: workspace/ is LOWER-level than config/ (config re-exports workspace types at lines 5-6); ideal naming inverted in greenfield (jackin-core > jackin-config) - Documents ideal 6-crate workspace: jackin-core, jackin-config, jackin-tui, jackin-runtime, jackin-console, jackin-shell + thin binary - Notes console/ has NO runtime/ import — cleanest pre-existing crate boundary - Bridge: incremental splits (4a, 4d, 4g) are pre-work toward workspace migration Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 38 — Rust workspace standards, community evidence Ground workspace recommendation in real-world project research: - ripgrep (9 crates), gitui (5 crates) went workspace due to library consumers - starship and fd-find stay single-crate at 1M+ LOC — no library use case - jackin (43K LOC, no external consumers) maps to starship/fd pattern → single-crate is community-standard; "stay single-crate" recommendation confirmed Update greenfield workspace structure to follow matklad's pattern: - Virtual manifest at root (no [package] in root Cargo.toml) - Flat crates/ directory (not nested); crate names match folder names - version = "0.0.0" for unpublished internal crates - Add inline dep comments to each crate in the ASCII structure Add research notes: ripgrep/starship/gitui/fd-find Cargo.toml findings + Cargo workspaces reference + matklad "Large Rust Workspaces" (2021-08-22) Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): revise §7.9 + §3 — adopt per-directory README.md §7.9: Reverse previous "reject" recommendation to "adopt" per-directory README.md for major src/ module directories. Rationale: README.md is AI-native — Claude Code, Copilot, Cursor load it automatically on directory entry, giving AI agents orientation before they decide which file to open. PROJECT_STRUCTURE.md being confirmed stale removes the main argument for the "single root file" approach. Add three-layer documentation model table: - README.md: directory orientation (AI + human, on entry) - AGENTS.md: agent workflow rules (root, session start) - CLAUDE.md: @AGENTS.md pointer only — NEVER add content here - //! docs: file-level contracts (when reading/editing) Add specific README.md content targets for 7 directories (src/, src/runtime/, src/console/, src/console/manager/, src/console/widgets/, docs/, docs/internal/). §3 target document shape: Add per-directory README.md to proposed hierarchy; add docs/internal/specs/ explicitly; note CLAUDE.md design principle (single-line @AGENTS.md — never duplicate content). Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): internal docs are browsable — unified Starlight site Operator directive: internal docs (architecture, specs, ADRs, roadmap) should be browsable, not hidden filesystem files. They are a different TYPE of docs focused on implementation details and vision, published as a "Developer Reference" section of the Starlight site. §3 target document shape: - docs/internal/ moves into docs/src/content/docs/internal/ (Starlight pages) - Browsable at jackin.tailrocks.com/internal/ - Sidebar: "Developer Reference" group (collapsed by default) with sub-sections for architecture, code-tour, contributing, testing, decisions, specs, roadmap - Include astro.config.ts sidebar config snippet §8.1 two-tier spec distinction eliminated: - Feature specs and behavioral specs both live at docs/src/content/docs/internal/specs/ - Type expressed via spec_type: behavioral | feature frontmatter, not filesystem location - Both browsable and searchable via Starlight; AI agents can be pointed to URLs §8.3 + §4: - All docs/internal/specs/ paths → docs/src/content/docs/internal/specs/ - ADRs: docs/internal/decisions/ → docs/src/content/docs/internal/decisions/ (browsable) - README.md pointer for src/runtime/ updated to URL reference Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): §11 — modern Rust docs platform (future project) Add §11 capturing the vision for a modern docs.rs alternative with: - rustdoc JSON ingestion → Astro Starlight presentation - MCP server for AI agent queries (Context7 alternative for Rust) - Rust-specific query types: rust_get_context(), rust_find_impls(), rust_search_types() — things Context7 cannot provide - Comparison table vs Context7 - Architecture diagram (ingestion → processing → Starlight + MCP) - Name candidates: rustlight, ferrodoc, cargo-starlight / starlight.rs - Note that jackin's §7.15 gen-rust-api.ts pipeline is the intentional prototype for the platform's processing and presentation layers Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 39 — update §0, fix stale internal/ paths §0 executive summary: rewrite to reflect decisions from iterations 30-38: - browsable internal docs (jackin.tailrocks.com/internal/) - per-directory README.md adoption (§7.9 reversed) - CLAUDE.md = @AGENTS.md single-line pointer only - greenfield workspace architecture (matklad's virtual manifest pattern) - §11 future project: modern Rust docs platform / Context7-for-Rust - document size 1800+ → 2200+ Fix stale docs/internal/ bare paths not caught by iteration 38 sweep: - Mermaid diagram: INTERNAL_ROADMAP, INTERNAL_CODE_TOUR → Starlight paths - §7.10 ADRs: docs/internal/decisions/NNN-title.md → .mdx Starlight path - §10 Track B item 2: op-picker spec path → Starlight MDX Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): iteration 40 — §7.15 pipeline + Rule 4 pub audit §7.15 (new): rustdoc JSON → Astro Starlight API documentation pipeline - Three options: rustdoc HTML publish / rustdoc JSON + bun script (recommended) / rustdoc-json crate as Rust binary - Option B recommended: matches existing docs/scripts/ pattern, nightly isolated to separate CI step, zero effect on stable build - Key design: URL at /internal/api/, cross-links to behavioral specs, Starlight unified search, prototype for §11 future project - Pub(crate) note: gen-rust-api.ts can feed Rule 4 visibility audit - Recommend: adopt after Phase 1 //! sprint (value ∝ coverage) §4 Rule 4 pub discipline: replace estimated "50-100 items" guess with verified numbers from iteration 40 grep: - 257 bare pub items, 21 pub(crate), 61 pub(super) across 94 files - 0 uses of unreachable_pub lint — no enforcement gate - Top violators: operator_env.rs (17), tui/output.rs (13), planner.rs (8) - Add concrete Cargo.toml [lints.rust] snippet: unreachable_pub = "warn" - Revised scope: ~150-200 mechanical conversions (excludes entry points) Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): split research into 19 actionable items Delete _research_notes.md (no longer needed). Replace 2343L READABILITY_AND_MODERNIZATION.md with: - README.md: index of all 19 items with phase, ordering notes, links - READABILITY_AND_MODERNIZATION.md: lightweight research summary (63L) - items/ITEM-001 through ITEM-019: individual actionable items Items by phase: Phase 1 (low risk, no confirmation): ITEM-001..004, 006..011 Phase 1 (needs confirmation): ITEM-005, 016, 018 Phase 2 (structural splits, confirmation required): ITEM-012..015 Phase 3 (deferred): ITEM-017 (rustdoc pipeline), ITEM-019 (workspace) Each item has: summary, key files with line numbers, steps, what needs confirmation, and relevant research backing from the 40-iteration analysis loop. Co-authored-by: Claude <noreply@anthropic.com> * docs(roadmap): migrate 19 items to Starlight reference/roadmap section Move all codebase health roadmap items from docs/internal/roadmap/items/ (plain Markdown, not browsable) to docs/src/content/docs/reference/roadmap/ (MDX pages, browsable at jackin.tailrocks.com/reference/roadmap/). Adds a new "Codebase health" sidebar group (Phase 1 → Phase 3) to astro.config.ts. Deletes the old items/ directory. Updates the internal README to redirect to the new location. Also adds codebase-readability.mdx — a new overview item that captures the overall readability/restructuring program with a recommended execution order: file splits first, then greenfield workspace, then per-directory README+AGENTS.md, then docs and specs. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> * docs(roadmap): remove premature internal/roadmap/README.md The internal/ structure doesn't exist yet — it will be created as part of the roadmap items themselves. No need for a redirect stub now. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> * docs(roadmap): remove READABILITY_AND_MODERNIZATION.md research archive All content has been distilled into the individual Starlight roadmap pages. The full 2343L research is preserved in git history at commit b7e9fc2. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> * docs(roadmap): fix check:repo-links errors + remove iteration log - Replace plain code spans with <RepoFile> for validate.rs, mise.toml, Cargo.toml, and op_picker/mod.rs - Remove deleted READABILITY_AND_MODERNIZATION.md reference from codebase-readability.mdx - Delete _iteration_log.md (git history is the archive) Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> * docs(roadmap): fix lychee false-positive link in move-contributing-testing The example redirect text contained a markdown hyperlink to a proposed future file path that doesn't exist yet. Changed to a code span. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> --------- Signed-off-by: Alexey Zhokhov <alexey@chainargos.com> Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Alexey Zhokhov <alexey@zhokhov.com> Co-authored-by: Codex <codex@openai.com>
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.
A substantial redesign of the operator console's workspace manager. The merged code is the source of truth; this description summarizes what landed across ~90 commits.
Series: PR 1 of 3 — toml_edit migration (#162) · PR 2 of 3 — workspace manager TUI (#166) · PR 3 of 3 — this PR
What's in this PR
Environments tab (renamed from "Secrets")
Per-workspace env-var management directly inside
jackin console. Two scopes:Operator interactions:
+ Add environment variable→ ScopePicker (All agents / Specific agent) → EnvKey → SourcePicker (Plain text / 1Password) → value modal or 1Password picker.Enteron the row. Op:// references are not text-editable — operator deletes (D) and re-adds via the picker for changes.D+ confirm.Mtoggles JUST the focused row. Op:// rows never mask (they're paths, not credentials).⚠warning + Enter blocked while invalid.<email> / <vault> / <item> → <field>(multi-account) or<vault> / <item> → <field>(single).[op]text marker on op:// keys for visual distinction; alphabetical sort across rows.*(mark), Space (allow/disallow auto-clears default).1Password picker
Multi-account-aware drill-down (Account → Vault → Item → Field). Single-account operators skip the Account pane.
Enteradvances;Escgoes back one pane.additional_informationsubtitle for disambiguation.rrefreshes any pane. Field values never enter the cache —RawOpFieldserde shape omits thevalueJSON key by design.OpCli::new_probe()) so a blockedopbinary cannot freezejackin consolestartup.Pkey and footer hint are suppressed whenopis unavailable.Inline agent disambiguation
The legacy full-screen
ConsoleStage::Agentpicker is deleted (~700 LOC removed). Replaced byModal::AgentPicker, an overlay on the manager list.Three-branch launch logic (
Enteron a workspace):default_agentset → immediate launch.Main-screen preview pane
Right-pane workspace summary: General → Mounts → Environments → Agents. Environments preview shows a flat alphabetical list with workspace-level and per-agent rows. Block omitted entirely when the workspace has no env at any scope.
UX rules codified in RULES.md
Filter:row → list body → standard footer.Polish & bug fixes
KeyModifiers::SHIFT.Qexit confirmation dialog outside the main screen.event::poll(50ms)+ per-tick worker-channel drain. Spinner advances without keypress.ConfirmSaverequired_height off-by-one fixed (last diff line was always clipped).Trust model
RawOpFielddeliberately omits thevalueJSON key — a unit test exhaustively destructuresOpFieldto fail compilation if avaluefield is ever added.op://references stored verbatim; resolution happens at launch, never in the editor.Tests
tests/manager_flow.rs— Environments-tab edit/delete/add, scope-first flow, picker open/cancel/commit, agent picker disambiguation, multi-account picker, save/exit cursor positioning.Out of scope (deferred)
op://path syntax.CHANGELOG.md(operator curates manually).secrets_flat_rowshot-path allocation cache (documented indocs/superpowers/plans/pr171-review-deferred.md).Test plan
cargo build --all-targets— cleancargo nextest run -p jackin— 1047/1047cargo clippy -- -D warnings— cleancargo fmt --check— cleanr), Esc back-navigation, failure states.Qexit dialog — main screen exits silently, anywhere else opens confirm.🤖 Generated with Claude Code