feat(history): mode-tagged history + top-of-chain journaling (#30)
Record the submission mode per history entry so advanced commands are reusable in simple mode, and fix the bug where a ':'-one-shot command lost its ':' across sessions (ADR-0052, closing #30). Format: the history.log status token gains an optional ':adv' suffix (ok / ok:adv / err / err:adv); 'source' stays last and canonical, so replay is unaffected. The in-memory ring (still Vec<String>) stores advanced entries ': '-prefixed; recall strips the ':' in advanced mode and keeps it in simple; hydration reconstructs the prefix from the tag. Journaling moved from the worker to the dispatch layer (spawn_dsl_- dispatch / run_replay / app-command sites), where the mode is in scope with no worker plumbing; finalize_persistence writes only yaml/csv (commit-db-last still atomic for state). The journal write is now best-effort (command already committed), consistent with the failure path. App commands journal simple, so they recall bare. Journaling is now uniform (every successful command, per ADR-0034) — closing a gap where show tables/relationships/explain didn't journal. Amends ADR-0034 (status tag + journaling location), ADR-0015 §6 (history.log out of the worker tx), ADR-0040 (journal-write best-effort). 15 worker-level journaling tests retired, re-covered at the new layer (history.rs format, app.rs recall matrix, iteration6 cross-session regression, replay). 2471 pass / 0 fail / 0 skip, clippy clean.
This commit is contained in:
@@ -213,6 +213,14 @@ working copy.
|
||||
|
||||
### 6. Persistence ordering
|
||||
|
||||
> **Amended by ADR-0052 (2026-06-13, issue #30):** `history.log` is no
|
||||
> longer written inside the worker transaction. It is a *journal* of typed
|
||||
> commands, not state, so success journaling moved to the dispatch layer
|
||||
> (next to the already-top-level failure journaling); `commit-db-last` now
|
||||
> governs the three **state** targets only (db + `project.yaml` +
|
||||
> `data/*.csv`), which still commit atomically in the worker. The journal
|
||||
> write is best-effort (amends ADR-0040).
|
||||
|
||||
A successful user command produces effects in four targets:
|
||||
the SQLite database, `project.yaml`, the relevant
|
||||
`data/<table>.csv` file(s), and `history.log`. INV-2 from the
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
Accepted. **Amended by ADR-0052 (2026-06-13, issue #30):** the status
|
||||
field gains an optional `:adv` mode suffix (`ok:adv` / `err:adv`) — the
|
||||
"non-breaking future extension" this ADR reserved — and **success
|
||||
journaling moves out of the worker to the dispatch layer**
|
||||
(`spawn_dsl_dispatch` / `run_replay` / app-command sites), next to the
|
||||
failure path, where the submission mode is in scope. `status_is_ok` keys
|
||||
off the base token, so `ok:adv` replays like `ok`.
|
||||
|
||||
## Context
|
||||
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
**Accepted** — 2026-05-30 (issue #9). Amends the output conventions of
|
||||
ADR-0014 (data operations), ADR-0028 (query plans / `explain`), and
|
||||
ADR-0019 (failure rendering); builds on ADR-0037's mode-tagged echo
|
||||
line.
|
||||
line. **Amended by ADR-0052 (2026-06-13, issue #30):** a `history.log`
|
||||
*journal*-write failure on a **successful** command is no longer fatal —
|
||||
journaling moved to the dispatch layer (after the db commit), so it is
|
||||
best-effort (logged + ignored), consistent with the failure-journal path.
|
||||
State-write failures (yaml/csv/db) remain fatal.
|
||||
|
||||
## Context
|
||||
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
# ADR-0052: Mode-tagged history for cross-mode recall
|
||||
|
||||
## Status
|
||||
|
||||
**Accepted + implemented 2026-06-13 (issue #30).** Closes Gitea **#30** —
|
||||
both the feature ("reuse advanced history commands in simple mode by
|
||||
prepending `:`") and the bug reported in its comment (the `:` one-shot
|
||||
prefix lost across sessions). All forks user-chosen before any code.
|
||||
**Amends ADR-0034** (journal status field gains a `:adv` tag; *journaling
|
||||
moves from the worker to the dispatch layer*), **ADR-0015 §5/§6**
|
||||
(history.log leaves the worker transaction — `commit-db-last` now scopes
|
||||
yaml/csv/db only), and **ADR-0040** (a success-path journal-write failure
|
||||
is best-effort, no longer fatal); references ADR-0003 (the `:` one-shot
|
||||
sigil). Plan: `docs/plans/20260613-issue-30-top-of-chain-journaling.md`
|
||||
(pre-build `/runda`, then a second `/runda` that drove the journaling
|
||||
relocation + the app-command exclusion). **2471 tests pass / 0 fail / 0
|
||||
skip (1 ignored), clippy clean.**
|
||||
|
||||
> **Why journaling moved (the key architectural turn).** The first draft
|
||||
> kept journaling in the worker and threaded the mode down to it (~30-site
|
||||
> plumbing). On review the user asked the right question: why is the
|
||||
> journal written deep in the worker at all, when the failure path already
|
||||
> journals at the top of the chain where command + mode + outcome are all
|
||||
> in scope? It shouldn't — `history.log` is a *journal of typed commands*,
|
||||
> not *state*. So success journaling moved up next to the failure path
|
||||
> (`spawn_dsl_dispatch` / `run_replay` / the app-command sites), the
|
||||
> mode-plumbing dilemma dissolved, and the worker's `finalize_persistence`
|
||||
> now writes only the state sources (yaml/csv). Consequence: the journal
|
||||
> write is best-effort (the command is already committed), consistent with
|
||||
> the failure path — see §5.
|
||||
|
||||
## Context
|
||||
|
||||
The input-history ring and `history.log` carry **no mode information**,
|
||||
which causes two coupled problems:
|
||||
|
||||
1. **Feature gap.** A command typed in advanced mode (`select * from T`)
|
||||
is stored bare. Recalled in simple mode it is not valid DSL → it just
|
||||
errors. There is no way to know it was an advanced (SQL) command and
|
||||
offer it back in a runnable form.
|
||||
|
||||
2. **Bug (issue #30 comment).** A `:`-one-shot advanced command in simple
|
||||
mode recalls correctly **in-session** (the in-memory ring stores the
|
||||
raw `:select 1`), but after quit+resume it comes back **without** the
|
||||
`:` and is unusable. Root cause: the ring stores the raw input
|
||||
(`:select 1`), but the worker journals the **stripped** `effective_input`
|
||||
(`select 1`) — submission strips the `:` before dispatch (ADR-0003) —
|
||||
so the on-disk `source` never carried the `:`, and hydration loses it.
|
||||
|
||||
Both reduce to: **history does not record the submission mode**, and the
|
||||
in-memory and on-disk representations disagree about the `:`.
|
||||
|
||||
## Decision
|
||||
|
||||
Record the **submission mode** per history entry, keep the on-disk
|
||||
`source` **canonical** (stripped — replay is unaffected), and have
|
||||
**recall reconstruct the runnable line** for the current mode.
|
||||
|
||||
### 1. In-memory ring stores the `:`-prefixed runnable form
|
||||
|
||||
`App.history` stays `Vec<String>` — no type change, so the public ring,
|
||||
the `ProjectSwitched` payload, and `seed_history` are untouched. An
|
||||
**advanced** entry is stored in its **simple-mode runnable form**, the
|
||||
`: `-prefixed string (e.g. `: select * from T`); a **simple** entry is
|
||||
stored bare. This is exactly what the in-session one-shot ring already
|
||||
does (`:select 1` recalls as typed) — generalised to *persistent*-advanced
|
||||
commands too, and made reconstructable on hydration. Because a simple
|
||||
DSL command can never begin with `:` (the sole sigil, ADR-0003), a
|
||||
leading `:` unambiguously marks an advanced entry.
|
||||
|
||||
`submit` builds the stored line from the submission: advanced →
|
||||
`": " + effective_input` (the `: ` matches the auto-space the typed
|
||||
one-shot inserts), simple → `effective_input`. This is computed **after**
|
||||
`effective_input` (today `push_history` runs on the raw `trimmed` before
|
||||
stripping; the reorder also drops a bare `:`, which never executed). The
|
||||
draft (`history_draft`) stays a plain `String`. `push_history` itself is
|
||||
unchanged — it still takes one `&str`.
|
||||
|
||||
### 2. Recall strips the `:` for advanced mode
|
||||
|
||||
`history_back` / `history_forward` set `self.input` from the stored
|
||||
string, then strip a leading `:` **iff the current persistent mode is
|
||||
Advanced**:
|
||||
|
||||
```
|
||||
if self.mode == Mode::Advanced && stored.starts_with(':') { stored[1..].trim_start() } else { stored }
|
||||
```
|
||||
|
||||
So an advanced entry recalls as `: select * from T` in **simple** mode
|
||||
(runs via the one-shot escape — the feature, and the cross-session bug
|
||||
fix) and bare `select * from T` in **advanced** mode (runs as SQL). A
|
||||
simple entry recalls bare in either mode (simple DSL already runs in
|
||||
advanced mode — issue #30). In-session and cross-session paths share the
|
||||
same stored form, so they finally agree.
|
||||
|
||||
### 3. On-disk: a mode tag in the status field
|
||||
|
||||
The record stays three pipe-separated fields `<ts>|<status>|<source>`
|
||||
(so `source` remains the last, pipe-tolerant, canonical field — replay
|
||||
reads it unchanged). The **status token** gains an optional `:adv`
|
||||
suffix:
|
||||
|
||||
| Submission | Success | Failure |
|
||||
|---|---|---|
|
||||
| Simple | `ok` | `err` |
|
||||
| Advanced (persistent or one-shot) | `ok:adv` | `err:adv` |
|
||||
|
||||
ADR-0034 §1 already reserved the status field for "additional values …
|
||||
a non-breaking future extension"; this is that extension. The status
|
||||
parser splits the token on `:`: the base (`ok`/`err`) gives replayability
|
||||
(`status_is_ok` ⇔ base == `ok`), the `adv` suffix gives the mode — so an
|
||||
unknown future token degrades to "not ok, simple" rather than mis-parsing.
|
||||
|
||||
### Journaling location: the dispatch layer, not the worker
|
||||
|
||||
Both tags are written **at the dispatch layer**, where command + mode +
|
||||
outcome are all in scope — so the mode needs no plumbing into the worker:
|
||||
|
||||
- **Success:** `spawn_dsl_dispatch`, immediately after
|
||||
`execute_command_typed` returns `Ok`, calls
|
||||
`append_history(source, submission_mode.is_advanced())` (best-effort).
|
||||
`run_replay` does the same per replayed line (tagged simple — replay is
|
||||
mode-agnostic), and the app-command sites (`perform_switch` /
|
||||
`spawn_export` / `spawn_rebuild`) journal **simple** (`advanced = false`
|
||||
— app commands run in any mode, so no `:` on recall; this also avoids a
|
||||
redundant `: undo`).
|
||||
- **Failure:** unchanged location (the App→`JournalFailure`→runtime path,
|
||||
already at the top), now carrying the mode — `JournalFailure` gains
|
||||
`advanced`, and `DslFailed` gains `submission_mode` for the
|
||||
worker-rejection sub-path (the parse-failure sub-path has it in
|
||||
`dispatch_dsl`). `Ok`/`Err` are exclusive, so success-in-spawn and
|
||||
failure-in-App-path never double-journal.
|
||||
|
||||
The worker's `finalize_persistence` and the four no-op-skip / three
|
||||
read-only sites **no longer journal** — they leave the state writes
|
||||
(yaml/csv) in the worker transaction and let the dispatch layer journal
|
||||
the `Ok` outcome.
|
||||
|
||||
### 4. Hydration reconstructs the `:`-prefixed form
|
||||
|
||||
`read_recent_sources` parses each record's status tag and, for an
|
||||
advanced record, **reconstructs** the `: `-prefixed string from the
|
||||
canonical `source` (`format!(": {source}")`); simple records pass through
|
||||
bare. It still returns `Vec<String>`, so `read_history_seed`,
|
||||
`seed_history`, and the `ProjectSwitched` payload are **unchanged**. A
|
||||
hydrated entry is therefore byte-identical to its in-session form, and
|
||||
recall behaves identically.
|
||||
|
||||
### Back-compatibility
|
||||
|
||||
Old `history.log` files have only `ok` / `err` tokens → parsed as
|
||||
`advanced = false` (simple). Their advanced commands stay un-`:`-able on
|
||||
recall — the pre-existing behaviour, not a regression; nothing migrates.
|
||||
`status_is_ok` keys off the base token, so `ok:adv` records replay
|
||||
exactly as `ok` does today (source is canonical either way).
|
||||
|
||||
### Journal write is best-effort (amends ADR-0040)
|
||||
|
||||
Because the journal is now written *after* the worker replies (i.e. after
|
||||
`tx.commit`), a journal-write failure can no longer roll the command back.
|
||||
It is **best-effort** — logged and ignored, exactly like the failure path
|
||||
already is (ADR-0034 §4) — so the two journal paths are finally
|
||||
consistent. State integrity is unchanged: yaml/csv/db still commit
|
||||
atomically in the worker (a *state*-write failure still rolls back and is
|
||||
fatal). The only property given up: on a rare journal-write failure (disk
|
||||
full) a committed command may be missing from `history.log` — not
|
||||
recallable/replayable next session, but the state is correct. User-chosen
|
||||
over keeping journaling coupled in the worker (which would have needed the
|
||||
~30-site mode plumbing). See the plan's §2 for the full trade-off.
|
||||
|
||||
## Forks (user-chosen)
|
||||
|
||||
- **Format = mode tag in the status field** (`ok:adv`/`err:adv`), over a
|
||||
new 4th field (ambiguous with unescaped pipes in old `source`s without
|
||||
a version bump) or a `:`-prefix in `source` (would make `source`
|
||||
non-canonical and force replay to strip it).
|
||||
- **Scope = unified** (bug + feature) over bug-only: one mechanism does
|
||||
both, and keeping `source` canonical for replay needs the mode tag
|
||||
regardless, so bug-only is barely smaller and leaves the main ask open.
|
||||
- **Journaling location = dispatch layer, best-effort** over keeping it
|
||||
worker-coupled-and-fatal (which needed the ~30-site mode plumbing). The
|
||||
user's architectural call (§Status).
|
||||
|
||||
## Consequences
|
||||
|
||||
- Advanced history is reusable in simple mode; the `:` one-shot survives
|
||||
resume. The in-memory and on-disk representations agree.
|
||||
- **Journaling left the worker.** `finalize_persistence` and the
|
||||
no-op-skip / read-only sites no longer journal; success is journalled at
|
||||
the dispatch layer (`spawn_dsl_dispatch` / `run_replay` / app-command
|
||||
sites). The ring stays `Vec<String>`; `seed_history` / `ProjectSwitched`
|
||||
are untouched. The vestigial worker `source` plumbing (the `_source`
|
||||
param on `finalize_persistence` / `do_rebuild_from_text` and the thin
|
||||
read-only `*_request` wrappers) is left in place — a clean follow-up.
|
||||
- **App commands recall bare.** Because they are dispatched outside the
|
||||
`ExecuteDsl`/spawn path, app commands journal **simple** (`advanced =
|
||||
false`) at their own sites, and `submit` excludes them from the ring's
|
||||
`advanced` flag (`!is_app_command`) — so `mode advanced` / `undo` recall
|
||||
bare and run fine in simple mode, with no redundant `:`.
|
||||
- **Journaling is now uniform (user-confirmed).** The spawn journals on
|
||||
`outcome.is_ok()`, so **every** successful command is recorded — closing
|
||||
a pre-existing gap where `show table` / `show data` / `select` journalled
|
||||
but `show tables`/`show relationships`/`show indexes`, `show relationship
|
||||
<name>`, and `explain` did **not** (their worker arms carried no
|
||||
`source` / no journal call). The new behaviour matches ADR-0034 §1
|
||||
("record every submitted command"); those reads are now recallable and
|
||||
are re-run harmlessly on replay (`explain` never executes; shows produce
|
||||
output, no state change). A DA finding, accepted as the more-correct
|
||||
behaviour over re-adding command-outcome gating to preserve the old
|
||||
inconsistency.
|
||||
- **Replay re-journaling.** When `replay` re-dispatches a line, the
|
||||
re-written record is tagged from how replay dispatched (mode-agnostic →
|
||||
`ok`), so a replayed advanced command may be re-journalled without
|
||||
`:adv`. Replay correctness of execution is unchanged (it already parses
|
||||
mode-agnostically); this only affects the *tag* of the re-written line.
|
||||
Noted; not addressed here (replay's own mode-fidelity is out of scope).
|
||||
|
||||
## Tests
|
||||
|
||||
- **Tier-1 (`app.rs`):** an advanced one-shot / persistent-advanced
|
||||
submission is stored `: `-prefixed; it recalls as `: …` in simple mode
|
||||
and bare in advanced mode; a simple entry recalls bare in both; a bare
|
||||
`:` is not stored; a parse-failure is still recallable; dedup/cap hold.
|
||||
- **Tier-1 (`history.rs`):** the writer emits `ok:adv`/`err:adv`;
|
||||
`read_recent_sources` reconstructs the `: `-prefix for `:adv` records
|
||||
and leaves `ok`/`err` records bare (so old logs read as simple);
|
||||
`status_is_ok` is true for `ok` and `ok:adv`.
|
||||
- **Tier-3 (`iteration6_resume_history` / it):** the headline
|
||||
**regression** — type a `:`-one-shot advanced command, journal +
|
||||
hydrate, and assert it recalls **with** `:` in simple mode (fails on
|
||||
current code); plus a persistent-advanced command round-tripping to a
|
||||
`: …` recall.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Replay re-journaling mode-fidelity (above).
|
||||
- Special-casing app commands to avoid the redundant recall `: `.
|
||||
- Distinguishing one-shot from persistent advanced on recall (both are
|
||||
simply "advanced" — the `:` is what simple mode needs either way).
|
||||
- A format version marker / pipe-escaping in `source` (unneeded — the
|
||||
status-tag approach keeps `source` last and canonical).
|
||||
@@ -57,3 +57,4 @@ This directory contains the project's ADRs, recorded per
|
||||
- [ADR-0049 — Input-field readline keymap: Esc-clear + Ctrl-A/E/W/K/U (I1b)](0049-input-field-readline-keymap.md) — **Accepted + implemented 2026-06-12 (issue #29)**, closes Gitea **#29** and the deferred **I1b** readline requirement. **Amends ADR-0046**, which listed "readline shortcuts (I1b)" as out-of-scope — that item is now in scope and decided here; orthogonal to ADR-0003's input-*mode* model and extends the I1a single-line cursor editing already shipped. Binds, in the input field (non-modal, non-nav, both modes): **`Esc`** clears a partly-typed command (empty buffer, cursor→0, scroll→0); **`Ctrl-A`/`Ctrl-E`** alias Home/End (line start/end); **`Ctrl-W`** deletes the previous word (readline-style — eats trailing whitespace then the preceding non-whitespace run, UTF-8-safe on char boundaries, only back to the cursor); **`Ctrl-K`** kills to end of line; **`Ctrl-U`** kills to start. **Esc precedence:** a live Tab-completion memo still wins (Esc undoes the completion first, ADR-0022; Esc clears only when no memo) — Esc-once backs out the completion, Esc-again clears. Forks all user-chosen: **single-Esc-clears** (not double-Esc — discoverable over accident-proof; an unsubmitted draft can be lost, a submitted line is always in history); the **full I1b set** (not just the issue's literal Ctrl-A/E + Esc); a **new ADR** (not an ADR-0046 amendment / no-ADR). Cursor-only keys (Ctrl-A/E) leave history navigation intact like Home/End; buffer-mutating keys (Esc-clear, Ctrl-W/K/U) end it like Backspace. Helpers `clear_input`/`delete_prev_word`/`kill_to_end`/`kill_to_start` in `src/app.rs`; **22 new Tier-1 tests, 2458 pass / 0 fail / 0 skip (1 ignored), clippy clean**. OOS: on-screen keybinding hints (issue #27 owns surfacing per-focus keybindings in the bottom status line — this ADR makes the keys *work*, #27 makes them *discoverable*); demo-mode badges for the new chords (ADR-0047 follow-up — Esc already badges `[ESC]`, the glyph-less Ctrl-chords are flagged but not added); multi-line input (I1); word-wise cursor motion (Alt-B/F) / transpose / yank
|
||||
- [ADR-0050 — Incidental-DDL confirmations omit relationship info (structure-only)](0050-incidental-ddl-confirmations-omit-relationships.md) — **Accepted + implemented 2026-06-12 (issue #28)**, closes Gitea **#28**. **Supersedes** the incidental-DDL clause of **ADR-0044 §1** and the relationship-block half of **ADR-0016 §5**. Incidental-DDL confirmation echoes (`create table`, `add`/`drop`/`rename`/`change column`, `add`/`drop index`) now render **structure only** — header + column box + `Indexes:` + constraints — with **no `References:` / `Referenced by:` block** (neither prose nor diagram), even when the table carries relationships the user did not touch. Rationale (owner): a confirmation echo reports the change just made, not untouched relationships; ADR-0044's terse prose was the lesser of "prose vs diagram", but the right answer for these surfaces is **neither**. **Relationship-subject surfaces are unchanged** — `show table`, `add`/`drop relationship`, `show relationship` still render ADR-0044 diagrams; relationships appear only when the user asks for (`show table`) or acts on (`add`/`drop relationship`) one, and are one `show table <T>` away — **no information lost**. Forks both user-chosen: **scope = all incidental DDL** (not just `add column` — the rationale is uniform, the mental model clean, and it's the simpler edit) and **delete the prose renderer** (not retain it dormant — no dead code). **Mechanism:** the `handle_dsl_success` `matches!` routing is unchanged (relationship-subject → diagrams; else → `render_structure`); the change is one line inside `render_structure` (`output_render.rs` — drop the relationship-block call) since all its callers are incidental DDL, plus deletion of the orphaned `relationship_prose_lines` + `cols_disp` helpers. The prose format survives in ADR-0016 §5 + git history for a future OOS-7 always-prose setting. **Tests:** the prose-presence unit test + its snapshot removed; a new unit test asserts `render_structure` on a description carrying **both** inbound and outbound relationships emits the box but no prose; the misnamed `add_relationship_flow_shows_inbound_section_on_parent` integration test (which sent an `AddColumn`) inverted + renamed to assert the add-column echo omits the prose; the diagram tests (`show table`, `add relationship`) unaffected. **2458 pass / 0 fail / 0 skip (1 ignored), clippy clean**. `requirements.md` unaffected (ADR-tracked refinement of a decided area, like ADR-0044 itself)
|
||||
- [ADR-0051 — Bottom keybinding strip: context- and state-aware](0051-context-state-aware-keybinding-strip.md) — **Accepted + implemented 2026-06-13 (issue #27)**, closes Gitea **#27**. Repurposes the bottom status line into a **keystrokes-only, state-selected** strip (builds on ADR-0046 nav focus, ADR-0003 modes, ADR-0049 the #29 readline keys it now advertises, ADR-0022 the completion memo). A pure `status_bar_bindings(app) -> Vec<(key,label)>` chooses the strip by **priority, first match wins**: (1) **sidebar focus** → `Ctrl-O next pane · ↑↓/PgUp/PgDn scroll · Esc input`; (2) **completion memo live** (`last_completion`) → `Tab/Shift-Tab cycle · Esc cancel · Enter run`; (3) **history navigation** (new `App::is_browsing_history()` exposing the private `history_cursor`) → `↑↓ browse · Esc clear · Enter run`; (4) **editing** (input non-empty) → `Esc clear · Ctrl-A/E home/end · Ctrl-W del word · Enter run` (surfaces the #29 keys, closing ADR-0049's deferred advertisement); (5) **default** (empty) → `Ctrl-O sidebar · Tab complete · ↑ history · Enter run`. Priority is correct because Up clears the completion memo and Tab cancels history nav, so states 2/3 never co-occur, and the five are exhaustive for Input focus. **Typed-command words leave the strip** (`mode advanced`/`mode simple` switch, `:` one-shot) and **mode discovery moves to the empty-input hint** (`resolve_hint_lines`), **simple mode only**: `\`mode advanced\` for SQL` (the verb "type" omitted — the prompt implies it; advanced mode shows **no** pointer per a post-trial user decision — a switcher knows how they got there and `help` covers the way back). The one-shot's old `Backspace cancel one-shot` label is subsumed by the editing state (behaviour intact). Forks all user-chosen: **editing state shows the #29 keys** (vs unadvertised); **`Ctrl-C quit` omitted** from the strip (vs always shown); **no width-drop machinery** — the longest strip (~65 cols) fits all supported widths, so a **width-budget unit test** keeps it lean by construction instead (the user's own observation). Catalog: 12 new `shortcut.*` labels + the `panel.hint_mode_advanced` string added to `en-US.yaml`+`keys.rs` (validator-checked 1:1), 5 now-dead strip strings removed. **Modal-aware strip is OOS** (pre-existing: a modal owns the keyboard and carries its own hints; the strip under it is unchanged-in-kind, not worsened). Tests: 9 Tier-1 unit (per-state key sets — completion/history driven through real key events; width budget; mode-pointer presence/absence), 1 Tier-3 rewritten (`status_bar_is_keystroke_only_and_state_aware`), 15 full-panel snapshots re-accepted (reviewed — strip/hint only). **2467 pass / 0 fail / 0 skip (1 ignored), clippy clean.** OOS: modal-aware strip; a full-key cheatsheet overlay; Ctrl-K/U advertisement (editing strip shows the highest-value subset within the width budget)
|
||||
- [ADR-0052 — Mode-tagged history for cross-mode recall](0052-mode-tagged-history-cross-mode-recall.md) — **Accepted + implemented 2026-06-13 (issue #30)**, closes Gitea **#30** — the feature (advanced history reusable in simple mode) **and** the bug in its comment (the `:` one-shot prefix lost across sessions). **Amends ADR-0034** (status field gains a `:adv` tag; **journaling moves from the worker to the dispatch layer**), **ADR-0015 §5/§6** (history.log leaves the worker transaction — `commit-db-last` now scopes yaml/csv/db only), and **ADR-0040** (a success-path journal-write failure is best-effort, not fatal); references ADR-0003. **Root cause:** history carried no mode, and the in-memory ring stored the raw `:select 1` while the worker journalled the *stripped* `select 1`, so the `:` was lost on disk. **Fix:** record the submission mode per entry as a **`:adv` suffix on the status token** (`ok`/`ok:adv`/`err`/`err:adv`) — `source` stays last + canonical so replay is unaffected; the in-memory ring (still `Vec<String>`) stores advanced entries in their `: `-prefixed simple-mode runnable form (a leading `:` unambiguously marks advanced since simple DSL never starts with `:`); recall **strips the `:` in advanced mode** (runs as bare SQL) and keeps it in simple mode (runs via the one-shot escape); hydration reconstructs the `: `-prefix from the tag, so cross-session = in-session. **The architectural turn (user's call):** the first draft kept journaling in the worker + threaded the mode down (~30-site plumbing); on review the user asked why the journal is written deep in the worker when the *failure* path already journals at the top of the chain — it shouldn't (history.log is a journal, not state). So **success journaling moved up** to `spawn_dsl_dispatch` / `run_replay` / the app-command sites (next to the failure path), the worker's `finalize_persistence` now writes only yaml/csv, and the journal write became **best-effort** (the command is already committed — consistent with the failure path; a rare disk-full leaves a committed command unjournalled, state intact). **App commands** journal simple (dispatched outside the spawn) and `submit` excludes them from the ring's advanced flag, so `undo`/`mode advanced` recall bare. Forks user-chosen: status-tag format (vs 4th field / `:`-in-source); unified scope; **dispatch-layer best-effort journaling** (vs worker-coupled-fatal). Two `/runda` passes (the second drove the relocation + app-command exclusion). Tests: the 15 worker-level journaling tests retired (worker no longer journals — yaml/csv/operation checks kept), re-covered at the new layer (history.rs status-tag + `:`-reconstruct; app.rs recall matrix; the #30 cross-session regression in `iteration6`; replay tests cover `run_replay` journaling). **2471 pass / 0 fail / 0 skip (1 ignored), clippy clean.** OOS: unwinding the now-vestigial worker `source` plumbing (`_source` params + thin `*_request` wrappers — a clean follow-up); replay re-journaling mode-fidelity (a replayed advanced line re-journals simple — not a regression)
|
||||
|
||||
Reference in New Issue
Block a user