okf-tui 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.okf/capabilities/index.md +13 -0
  3. data/.okf/capabilities/okf-surface.md +48 -0
  4. data/.okf/capabilities/views.md +48 -0
  5. data/.okf/decisions/invents-no-analysis.md +8 -7
  6. data/.okf/decisions/no-version-ceilings.md +8 -9
  7. data/.okf/decisions/okf-capability-drift.md +16 -18
  8. data/.okf/decisions/one-door-the-plugin-seam.md +15 -14
  9. data/.okf/decisions/registry-write-boundary.md +15 -14
  10. data/.okf/decisions/ruby-floor.md +11 -9
  11. data/.okf/decisions/search-facade-coupling.md +22 -24
  12. data/.okf/decisions/undeclared-width-dependency.md +12 -10
  13. data/.okf/index.md +13 -5
  14. data/.okf/interaction/cross-bundle-scope.md +8 -8
  15. data/.okf/interaction/deferred-search.md +8 -6
  16. data/.okf/interaction/esc-peels-one-layer.md +10 -9
  17. data/.okf/interaction/filter-escalates-to-search.md +12 -11
  18. data/.okf/interaction/following-links.md +10 -8
  19. data/.okf/interaction/key-routing.md +6 -5
  20. data/.okf/interaction/which-registry.md +14 -15
  21. data/.okf/log.md +22 -0
  22. data/.okf/rendering/ansi-aware-width.md +8 -7
  23. data/.okf/rendering/fit-or-say-so.md +34 -0
  24. data/.okf/rendering/index.md +1 -0
  25. data/.okf/rendering/markdown-rendering-trap.md +8 -7
  26. data/.okf/rendering/status-vocabulary.md +6 -5
  27. data/.okf/rendering/whole-frame-painting.md +8 -6
  28. data/.okf/structure/doors.md +61 -0
  29. data/.okf/structure/index.md +16 -0
  30. data/.okf/structure/rendering.md +58 -0
  31. data/.okf/structure/the-app.md +55 -0
  32. data/.okf/structure/the-workspace.md +69 -0
  33. data/.okf/testing/adding-a-view.md +58 -0
  34. data/.okf/testing/ci-matrix.md +8 -6
  35. data/.okf/testing/headless-frames.md +6 -6
  36. data/.okf/testing/index.md +2 -0
  37. data/.okf/testing/pty-test.md +6 -5
  38. data/.okf/testing/the-suite.md +120 -0
  39. data/CHANGELOG.md +57 -1
  40. data/README.md +7 -2
  41. data/lib/okf/tui/version.rb +1 -1
  42. metadata +18 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: def7a9aa9c4d6fd614896848e6e6712491f062f830d487e260142bfdd8698374
4
- data.tar.gz: 96e3fb4d8bdbb6c78aef7cf00392372667d89066793bd81feacb6e486e13967a
3
+ metadata.gz: c091f949e3ec53c2374154e8c006a32780d4051bd30949d4bd100da01cc3f178
4
+ data.tar.gz: 685a0064a008179fe03ee3321b135e978998894a279498cdc1354148032af12b
5
5
  SHA512:
6
- metadata.gz: 9520521f0c1ca38385d2ab974b81d1f574923be0ae9f4612c4dd590d66d11a92dbc0e88ac57d81793944134b349df664e802d102609d24d899bb7bf577aeb383
7
- data.tar.gz: f8f24b77abed3ef919c40ff34d322ff81d006be77f27c2daf1bb1377bc1634d8834eaa80cd4d93ad987f57a13bd08bce499166a01ebb04578cc6874ed05d5223
6
+ metadata.gz: 0e1b81de7727c72fb6ec150485bc2a555ee0238fe3b0636e57520b1ef714f804b1f44cf33bd14d3eba0b235f9dc2cefdd5d195a8f85a0838cc74e819f9ad7bc6
7
+ data.tar.gz: ddce9998f93e6a1259bb3972600ddb1cca8bcd2458d8134d91cd0a4ca966f935949a08bdb40034fc12bcff08f755b495a30f41292fa1216a48eeea0128c56dca
@@ -0,0 +1,13 @@
1
+ # Capabilities
2
+
3
+ What this program can already answer, and where the answer comes from. Read it
4
+ before building a view, a panel or a computation — the point of a catalogue is
5
+ that the seventh screen is not the third screen under a new name, and that a
6
+ number on it is one okf computed.
7
+
8
+ `test/unit/bundle_catalog_test.rb` compares the view catalogue against
9
+ `App::TABS`, so a view added to the code and not to the catalogue is a red suite
10
+ rather than a stale document.
11
+
12
+ * [The Six Views](views.md) - What each screen answers, which keys reach it, and which reader backs it.
13
+ * [The okf Surface](okf-surface.md) - Every kernel call this gem makes, and the rule that keeps the list short.
@@ -0,0 +1,48 @@
1
+ ---
2
+ type: Capability
3
+ title: The okf Surface This Gem Uses
4
+ description: Every kernel call the TUI makes, in one list — because the rule is that okf owns every judgement on screen, and a number this gem computed is a number that will disagree with `okf lint`.
5
+ tags: [okf, boundary, capabilities]
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-08-19
9
+ ---
10
+
11
+ # The rule first
12
+
13
+ **This program invents no analysis.** Everything on screen is a pure call on an
14
+ in-memory bundle the kernel built. The argument, and what it costs, is
15
+ [invents-no-analysis](/decisions/invents-no-analysis.md).
16
+
17
+ So this list is short on purpose, and the right instinct on finding it does not
18
+ cover your case is to ask whether the kernel should answer it — not to compute it
19
+ here.
20
+
21
+ # What is actually called
22
+
23
+ | kernel surface | reached through | shows up as |
24
+ |---|---|---|
25
+ | `Bundle` / reader | `Model#catalog` | every row in browse |
26
+ | `Bundle::Graph` | `Model#graph` | the graph view, `hubs`, `orphan_ids`, `edge_count` |
27
+ | `Bundle::Validator` | `Model#validation` | health's conformance findings |
28
+ | `Bundle::Linter` | `Model#lint`, `#skipped_checks` | health's curation findings, and the confession of what did not run |
29
+ | `Bundle::Search` | `Workspace#search` | the search view, across the whole scope |
30
+ | `Registry` | `Workspace` | the bundles view, and the only writes this gem makes |
31
+ | `CLI::Command` ref grammar | `Refs` | `@slug`, bare `@`, `@group`, and the refusal of `@all` |
32
+ | `Concept` trust/status | `Model#trust_posture`, `#status_posture` | the v0.2 chips and the one verdict |
33
+
34
+ Two of those are pinned against the CLI rather than trusted: the directory tree
35
+ is asserted against `okf dirs --json`, and the cohesion table against
36
+ `okf graph --traffic`, so the screen and the command cannot drift apart.
37
+
38
+ # The two probes
39
+
40
+ `OKF::TUI.search_capable?` and `.spec_capable?` ask the *installed* okf what it
41
+ can do, rather than inferring it from a version number. That is
42
+ [okf-capability-drift](/decisions/okf-capability-drift.md), and it is why this
43
+ gem carries [no version ceilings](/decisions/no-version-ceilings.md): a probe
44
+ degrades a feature, a ceiling refuses to install.
45
+
46
+ The one place the coupling is sharper than a probe can cover is the search
47
+ facade — [search-facade-coupling](/decisions/search-facade-coupling.md) is the
48
+ edge, and it is a private-surface dependency pinned by name in the suite.
@@ -0,0 +1,48 @@
1
+ ---
2
+ type: Capability
3
+ title: The Six Views
4
+ description: What each screen answers, whether it is about one bundle or the whole scope, which keys reach it, and which reader supplies its numbers.
5
+ tags: [views, keyboard, capabilities]
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-08-19
9
+ ---
10
+
11
+ # The catalogue
12
+
13
+ `App::TABS` is the source of truth for this table, and a test holds the two
14
+ together.
15
+
16
+ | view | key | answers | reads |
17
+ |---|---|---|---|
18
+ | `bundles` | `1` | what can I open, which is active, which is the default — the registry's groups, and all registry config | `Workspace` |
19
+ | `browse` | `2` | what is in the bundle, in reading order — `index.md`, `log.md`, then each directory | `Model#rows`, `#dirs` |
20
+ | `search` | `3` | which concept covers X, across *every bundle in scope*, ranked together | `Workspace#search` |
21
+ | `graph` | `4` | what shape is its knowledge — narrowed by type, tag or dir | `Model#graph`, `#types`, `#tags` |
22
+ | `health` | `5` | is it legal, well curated, well structured — findings left, standing right | `Model#validation`, `#lint`, `#hubs`, `#dir_traffic`, `#dir_arcs` |
23
+ | `help` | `6` | the keys | — |
24
+
25
+ # Which of them are about *one* bundle
26
+
27
+ `browse`, `graph` and `health` are `SINGLE_BUNDLE_VIEWS`: they follow the
28
+ **active** bundle, and they have nothing to show when none can be read. `search`
29
+ follows the **scope** instead — a different, independent axis, so you can read
30
+ one bundle while searching all of them. `bundles` is where both are set.
31
+
32
+ That two-axis model is the thing most likely to be got wrong by a new view, and
33
+ [cross-bundle-scope](/interaction/cross-bundle-scope.md) is the argument for it.
34
+
35
+ # The two behaviours every view inherits
36
+
37
+ `health` and `help` are `CONTENT_VIEWS` — a single scrolling page rather than a
38
+ selectable list, which changes what the arrow keys mean.
39
+
40
+ `bundles`, `browse` and `graph` are `FILTERABLE_VIEWS` — `/` starts typing, and
41
+ the filter belongs to the view, so switching away drops it. When a browse filter
42
+ matches nothing, `Enter` carries the term to search, because that is the query
43
+ the reader was already making:
44
+ [filter-escalates-to-search](/interaction/filter-escalates-to-search.md).
45
+
46
+ A seventh view is a real cost on a six-tab bar, and one has already been
47
+ withdrawn for not earning its tab. Before adding one, check that an existing
48
+ view with a key the reader already has does not answer the same question.
@@ -3,7 +3,14 @@ type: Decision
3
3
  title: It Invents No Analysis
4
4
  description: Every judgement on screen is a pure call into okf; the TUI adds no check, score, or verdict of its own, which bounds what a feature request may be answered with.
5
5
  tags: [okf-coupling, dependencies]
6
- timestamp: 2026-07-19
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-07-19
9
+ sources:
10
+ - title: "`lib/okf/tui.rb` — the module comment stating the split."
11
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/lib/okf/tui.rb
12
+ - title: "`lib/okf/tui/model.rb`, `lib/okf/tui/workspace.rb` — every analysis is a memoized call into okf."
13
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/lib/okf/tui/model.rb
7
14
  ---
8
15
 
9
16
  # Overview
@@ -45,9 +52,3 @@ construction* — implementing a fallback search here would have broken this rul
45
52
  That one has since resolved (okf 1.9.0 shipped the facade), which is the point
46
53
  rather than a footnote: the cost of this rule is paid in **waiting**, not in
47
54
  capability. The wait ended, and nothing had to be built twice.
48
-
49
- # Citations
50
-
51
- [1] `lib/okf/tui.rb` — the module comment stating the split.
52
- [2] `lib/okf/tui/model.rb`, `lib/okf/tui/workspace.rb` — every analysis is a
53
- memoized call into okf.
@@ -3,7 +3,14 @@ type: Decision
3
3
  title: No Version Ceilings on the Markdown Stack
4
4
  description: Pinning kramdown and rouge to their old lines to protect the Ruby 2.4 floor broke modern Ruby instead; the gems declare their own floors, so resolution handles it per-Ruby.
5
5
  tags: [ruby-floor, dependencies]
6
- timestamp: 2026-07-18
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-07-18
9
+ sources:
10
+ - title: "`okf-tui.gemspec` — the two floors, with the comment recording the failed ceiling attempt."
11
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/okf-tui.gemspec
12
+ - title: "Verified in `ruby:2.4` (Docker): resolved kramdown 2.4.0 + rouge 3.30.0, 34 runs / 6622 assertions green, and green across the whole matrix up to 4.0."
13
+ resource: "Verified in `ruby:2.4` (Docker): resolved kramdown 2.4.0 + rouge 3.30.0, 34 runs / 6622 assertions green, and green across the whole matrix up to 4.0"
7
14
  ---
8
15
 
9
16
  # Overview
@@ -59,11 +66,3 @@ So `< 3` buys the one thing a floor cannot: it turns the *next* one into a
59
66
  resolution failure a maintainer reads, instead of a wrong number a reader
60
67
  believes. `test/unit/gemspec_test.rb` pins both bounds — the floor may never lag
61
68
  the kernel this gem develops against, and the ceiling may not quietly go away.
62
-
63
- # Citations
64
-
65
- [1] `okf-tui.gemspec` — the two floors, with the comment recording the failed
66
- ceiling attempt.
67
- [2] Verified in `ruby:2.4` (Docker): resolved kramdown 2.4.0 + rouge 3.30.0,
68
- 34 runs / 6622 assertions green, and green across
69
- [the whole matrix](/testing/ci-matrix.md) up to 4.0.
@@ -3,7 +3,22 @@ type: Decision
3
3
  title: okf Moves, and This Breaks Quietly
4
4
  description: Inventing no analysis means every answer on screen is okf's — so okf renaming a field or adding a faster path is a change to this program, and every such drift found so far failed silently rather than loudly.
5
5
  tags: [okf-coupling, dependencies, testing, maintenance]
6
- timestamp: 2026-08-13
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-08-13
9
+ sources:
10
+ - title: "`okf-tui.gemspec` — the seven capabilities and the version each shipped in."
11
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/okf-tui.gemspec
12
+ - title: "`test/integration/dirs_test.rb` — `okf_subtree_counts`, the `okf dirs --json` oracle; and the `nested` fixture, which exists because at one directory level `dir` and `top_dir` are the same string and an assertion cannot tell them apart."
13
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/test/integration/dirs_test.rb
14
+ - title: "`test/integration/structure_test.rb` — `okf_traffic_rows`, parsing okf's human traffic table; the row count is asserted first so the comparison loop cannot pass vacuously."
15
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/test/integration/structure_test.rb
16
+ - title: "`lib/okf/tui/refs.rb` — the inherited grammar, and why it subclasses Command without ever being registered."
17
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/lib/okf/tui/refs.rb
18
+ - title: "okf `CHANGELOG.md`, 1.12.0 \"Changed\": \"The derived `area` field is renamed `top_dir`\" — \"`area` was never the OKF spec's word (`grep -ci area SPEC.md` → 0)\"."
19
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/CHANGELOG.md
20
+ - title: "Measured 2026-08-13 over five registered bundles / 129 concepts: first query 391.8 ms, then 16.2 / 13.0 / 12.0 / 14.4 ms. Before, every query paid the first figure."
21
+ resource: "Measured 2026-08-13 over five registered bundles / 129 concepts: first query 391.8 ms, then 16.2 / 13.0 / 12.0 / 14.4 ms. Before, every query paid the first figure"
7
22
  ---
8
23
 
9
24
  # Overview
@@ -101,20 +116,3 @@ was a *number this program did not have to compute*, and three of the four were
101
116
  okf getting better at answering it. A TUI with its own catalog would not have had
102
117
  the `area` break — it would have had its own, permanently, with no upstream to
103
118
  inherit the fix from.
104
-
105
- # Citations
106
-
107
- [1] `okf-tui.gemspec` — the seven capabilities and the version each shipped in.
108
- [2] `test/integration/dirs_test.rb` — `okf_subtree_counts`, the `okf dirs --json`
109
- oracle; and the `nested` fixture, which exists because at one directory level
110
- `dir` and `top_dir` are the same string and an assertion cannot tell them apart.
111
- [3] `test/integration/structure_test.rb` — `okf_traffic_rows`, parsing okf's human
112
- traffic table; the row count is asserted first so the comparison loop cannot
113
- pass vacuously.
114
- [4] `lib/okf/tui/refs.rb` — the inherited grammar, and why it subclasses Command
115
- without ever being registered.
116
- [5] okf `CHANGELOG.md`, 1.12.0 "Changed": "The derived `area` field is renamed
117
- `top_dir`" — "`area` was never the OKF spec's word (`grep -ci area SPEC.md` → 0)".
118
- [6] Measured 2026-08-13 over five registered bundles / 129 concepts: first query
119
- 391.8 ms, then 16.2 / 13.0 / 12.0 / 14.4 ms. Before, every query paid the first
120
- figure.
@@ -3,7 +3,20 @@ type: Decision
3
3
  title: One door — the plugin seam is the entry point
4
4
  description: okf-tui ships no executable; `okf tui` is the only way in, and the registration stays cheap enough that a run wanting `okf lint` pays nothing for it.
5
5
  tags: [okf-coupling, dependencies, cli]
6
- timestamp: 2026-07-19T18:00:00Z
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-07-19T18:00:00Z
9
+ sources:
10
+ - title: "`lib/okf/plugin.rb` — the whole seam, including why the heavy require is in `#call`."
11
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/lib/okf/plugin.rb
12
+ - title: "`okf-tui.gemspec` — the comment recording why there is no `spec.executables`."
13
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/okf-tui.gemspec
14
+ - title: "`test/integration/plugin_test.rb` — the dispatcher adding nothing but the streams, and the subprocess check that registering does not load tty-box."
15
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/test/integration/plugin_test.rb
16
+ - title: "`test/integration/terminal_test.rb` — okf's own executable on a real pty, the one test that walks process boot and plugin discovery rather than the in-process dispatcher."
17
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/test/integration/terminal_test.rb
18
+ - title: "Verified 2026-07-19: `okf --help` lists `tui` under `installed extensions:`; `okf validate` still runs without triggering discovery at all."
19
+ resource: "Verified 2026-07-19: `okf --help` lists `tui` under `installed extensions:`; `okf validate` still runs without triggering discovery at all"
7
20
  ---
8
21
 
9
22
  # Overview
@@ -20,7 +33,7 @@ simply inert.
20
33
  Installing the gem is the whole installation. There is no config file, no
21
34
  `okf plugin add`, and nothing to edit in okf — which is the point of a convention
22
35
  over a list. See okf's
23
- [extension points](https://github.com/serradura/okf-gem/blob/main/.okf/design/extension-points.md).
36
+ [extension points](https://github.com/serradura/okf/blob/main/.okf/design/extension-points.md).
24
37
 
25
38
  # One front end, because two would drift
26
39
 
@@ -117,15 +130,3 @@ The library keyword is untouched: `App` and `Workspace` still take `home:`, whic
117
130
  is how an embedding app and the suite name a registry without mutating a
118
131
  process-global. That is exactly the split okf itself draws — the env var is the
119
132
  CLI's only lever, the keyword is the library's.
120
-
121
- # Citations
122
-
123
- [1] `lib/okf/plugin.rb` — the whole seam, including why the heavy require is in `#call`.
124
- [2] `okf-tui.gemspec` — the comment recording why there is no `spec.executables`.
125
- [3] `test/integration/plugin_test.rb` — the dispatcher adding nothing but the
126
- streams, and the subprocess check that registering does not load tty-box.
127
- [4] `test/integration/terminal_test.rb` — okf's own executable on a real pty, the
128
- one test that walks process boot and plugin discovery rather than the
129
- in-process dispatcher.
130
- [5] Verified 2026-07-19: `okf --help` lists `tui` under `installed extensions:`;
131
- `okf validate` still runs without triggering discovery at all.
@@ -3,7 +3,20 @@ type: Decision
3
3
  title: It Writes the Registry, Never a Bundle
4
4
  description: The line the TUI's side effects sit on is not read-versus-write — it is the registry versus the knowledge; what that admits, what it excludes, and why `registry init` is on the far side of it.
5
5
  tags: [registry, boundary, side-effects]
6
- timestamp: 2026-08-13
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-08-13
9
+ sources:
10
+ - title: "`lib/okf/tui/workspace.rb` — the seven writes, each returning a status message and reloading; `not_registry_backed` for the ad-hoc case, which has no registry to change."
11
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/lib/okf/tui/workspace.rb
12
+ - title: "`AGENTS.md` — \"The registry is the user's configuration\", the same boundary where a change is judged."
13
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/AGENTS.md
14
+ - title: "`grep -rn \"Bundle::Writer\" lib/` → no matches, 2026-08-13."
15
+ resource: "`grep -rn \"Bundle::Writer\" lib/` → no matches, 2026-08-13"
16
+ - title: "okf `CHANGELOG.md` 1.12.0 — `okf registry init`, and the discovery rule that makes the nearest `.okf-registry.json` win."
17
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/CHANGELOG.md
18
+ - title: "`test/test_helper.rb` — `with_registry`, `with_local_registry`, and the `$OKF_HOME` save/restore around both."
19
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/test/test_helper.rb
7
20
  ---
8
21
 
9
22
  # Overview
@@ -21,7 +34,7 @@ The line is **the registry, not read-versus-write**: this program edits the user
21
34
 
22
35
  Seven of okf's eight `registry` verbs have a place here, and it is the same place —
23
36
  the bundles view, where `Workspace` owns every write and each is followed by a
24
- reload, per `AGENTS.md` constraint 8:
37
+ reload, per `AGENTS.md`'s rule that the registry is the user's configuration:
25
38
 
26
39
  | Key | `Workspace` | okf CLI |
27
40
  |-----|-------------|---------|
@@ -161,15 +174,3 @@ because okf's own API refuses to produce them: a member naming nothing registere
161
174
  and a cycle (`group cycle: @g would contain itself`). Both are legitimate things
162
175
  to survive, since okf invites you to *commit* a local registry — so a hand-edited
163
176
  file is a normal input, not an abuse.
164
-
165
- # Citations
166
-
167
- [1] `lib/okf/tui/workspace.rb` — the seven writes, each returning a status message
168
- and reloading; `not_registry_backed` for the ad-hoc case, which has no registry
169
- to change.
170
- [2] `AGENTS.md` constraint 8 — the same boundary, stated where a change is judged.
171
- [3] `grep -rn "Bundle::Writer" lib/` → no matches, 2026-08-13.
172
- [4] okf `CHANGELOG.md` 1.12.0 — `okf registry init`, and the discovery rule that
173
- makes the nearest `.okf-registry.json` win.
174
- [5] `test/test_helper.rb` — `with_registry`, `with_local_registry`, and the
175
- `$OKF_HOME` save/restore around both.
@@ -3,14 +3,21 @@ type: Decision
3
3
  title: Ruby 2.4 Floor, Inherited
4
4
  description: okf-tui takes okf's floor rather than its own, which costs the ergonomic syntax and forces the development tooling to load conditionally.
5
5
  tags: [ruby-floor, dependencies]
6
- timestamp: 2026-07-18
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-07-18
9
+ sources:
10
+ - title: "`okf-tui.gemspec`, `Gemfile`, `Rakefile`."
11
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/okf-tui.gemspec
12
+ - title: "Verified in `ruby:2.4` (Docker) and on CI, which runs 2.4 through 4.0."
13
+ resource: "Verified in `ruby:2.4` (Docker) and on CI, which runs 2.4 through 4.0"
7
14
  ---
8
15
 
9
16
  # Overview
10
17
 
11
- `required_ruby_version = ">= 2.4.0"`, taken from [okf](@okf), which takes it from
12
- rack. A UI over a library that runs on the Ruby an OS already ships would be a
13
- strange thing to require a newer Ruby for.
18
+ `required_ruby_version = ">= 2.4.0"`, taken from okf (`@okf design/ruby-floor`),
19
+ which takes it from rack. A UI over a library that runs on the Ruby an OS
20
+ already ships would be a strange thing to require a newer Ruby for.
14
21
 
15
22
  The cost lands in two places that are not obvious until you hit them.
16
23
 
@@ -52,8 +59,3 @@ the floor.
52
59
 
53
60
  Nothing is pinned *down* to make this work — see
54
61
  [no-version-ceilings](/decisions/no-version-ceilings.md).
55
-
56
- # Citations
57
-
58
- [1] `okf-tui.gemspec`, `Gemfile`, `Rakefile`.
59
- [2] Verified in `ruby:2.4` (Docker) and on CI, which runs 2.4 through 4.0.
@@ -3,7 +3,24 @@ type: Decision
3
3
  title: The Search Facade Coupling
4
4
  description: The search view rides okf's engine facade — `across` for the routing, and since okf 1.11.0 a corpus prepared once and queried many times; the `fuzzy` flag is what selects the engine, and it is load-bearing in a way it does not look.
5
5
  tags: [okf-coupling, search, dependencies]
6
- timestamp: 2026-08-13
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-08-13
9
+ sources:
10
+ - id: "1"
11
+ title: "Verified 2026-07-19 in a clean `ruby:3.2-slim` container — no checkout, no bundler: `gem install okf` resolved 1.9.0, `Bundle::Search.respond_to?(:across)` → `true`, `engine_for([:fuzzy])` → `OKF::Bundle::Search::Index`. RubyGems lists okf 1.9.0 as the current release."
12
+ resource: "Verified 2026-07-19 in a clean `ruby:3.2-slim` container — no checkout, no bundler: `gem install okf` resolved 1.9.0, `Bundle::Search.respond_to?(:across)` → `true`, `engine_for([:fuzzy])` → `OKF::Bundle::Search::Index`. RubyGems lists okf 1.9.0 as the current release"
13
+ - id: "2"
14
+ title: "Verified 2026-07-19 against the okf checkout: `engine_for([:fuzzy])` → `index`, `engine_for([])` → `scan`; `DEFAULT_ENGINE = :scan` in `lib/okf/bundle/search.rb`."
15
+ resource: https://github.com/serradura/okf/blob/main/gems/okf/lib/okf/bundle/search.rb
16
+ - id: "3"
17
+ title: "Measured 2026-08-13 against the registry's own five bundles (129 concepts): `across` per query 391.8 ms; with a held corpus 16.2 / 13.0 / 12.0 / 14.4 ms after the first. On the six test fixtures (36 concepts): 62–80 ms against 2.7–3.0 ms."
18
+ resource: "Measured 2026-08-13 against the registry's own five bundles (129 concepts): `across` per query 391.8 ms; with a held corpus 16.2 / 13.0 / 12.0 / 14.4 ms after the first. On the six test fixtures (36 concepts): 62–80 ms against 2.7–3.0 ms"
19
+ - id: "4"
20
+ title: "Reproduced 2026-07-18: `ruby -Ilib` outside bundler loaded okf 1.8.0 from the mise gem path, `Bundle::Search.respond_to?(:across)` → `false`, search returned 0 hits for a term the checkout finds. Under `bundle exec` the same query returned 1 hit (\"orphan\") and 11 (\"registry\")."
21
+ resource: "Reproduced 2026-07-18: `ruby -Ilib` outside bundler loaded okf 1.8.0 from the mise gem path, `Bundle::Search.respond_to?(:across)` → `false`, search returned 0 hits for a term the checkout finds. Under `bundle exec` the same query returned 1 hit (\"orphan\") and 11 (\"registry\")"
22
+ - title: "`lib/okf/tui.rb` — `OKF::TUI.search_capable?`."
23
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/lib/okf/tui.rb
7
24
  ---
8
25
 
9
26
  # Overview
@@ -13,7 +30,7 @@ merges several bundles into **one** ranked corpus.
13
30
 
14
31
  **This has shipped.** `across` was unreleased when the view was built — `okf`
15
32
  1.8.0 on RubyGems had no such method — but okf **1.9.0 carries it**, verified
16
- from a clean install with no checkout and no bundler in sight.[1] The coupling
33
+ from a clean install with no checkout and no bundler in sight.[^1] The coupling
17
34
  that shaped this file is resolved.
18
35
 
19
36
  The coupling is deliberate, because per-bundle indexes would be a different and
@@ -35,7 +52,7 @@ OKF::Bundle::Search.across(pairs, terms, fuzzy: true)
35
52
  ```
36
53
 
37
54
  The registry picks the default engine first and falls through to one that can
38
- answer, so `fuzzy` is what routes this to minifts.[2] That makes the flag
55
+ answer, so `fuzzy` is what routes this to minifts.[^2] That makes the flag
39
56
  load-bearing in a way it does not look: **dropping `fuzzy: true` would silently
40
57
  change the engine**, and with it the ranking — no error, no missing method, just
41
58
  different results and no BM25 scores. The screen would still work.
@@ -56,7 +73,7 @@ OKF::Bundle::Search.with(corpus, terms, fuzzy: true) # per query
56
73
  ```
57
74
 
58
75
  Measured over five registered bundles, 129 concepts: **392 ms** for the first
59
- query, then **12–16 ms**. Before, every query paid the 392 ms.[3] It is the same
76
+ query, then **12–16 ms**. Before, every query paid the 392 ms.[^3] It is the same
60
77
  arithmetic okf used to justify the opposite default — an index build amortized over
61
78
  one query is a bad trade, and over many it is the whole point.
62
79
 
@@ -113,7 +130,7 @@ into "no matches".
113
130
 
114
131
  That is exactly how it presented: running the CLI via `ruby -Ilib` outside
115
132
  bundler let RubyGems activate the installed `okf` 1.8.0, and search silently
116
- found nothing.[4] The prototype could never hit it — it put the okf checkout on
133
+ found nothing.[^4] The prototype could never hit it — it put the okf checkout on
117
134
  `$LOAD_PATH` directly, so it always had the unreleased method.
118
135
 
119
136
  So the check moved out of the rescue and up to boot:
@@ -125,22 +142,3 @@ OKF::TUI.search_capable? # across, prepare and with — all three
125
142
  The CLI refuses to start and exits `1`, naming **the okf file that answered** —
126
143
  not the version, the file — because the usual cause is a second okf ahead of the
127
144
  intended one on the load path, and a version number does not tell you that.
128
-
129
- # Citations
130
-
131
- [1] Verified 2026-07-19 in a clean `ruby:3.2-slim` container — no checkout, no
132
- bundler: `gem install okf` resolved 1.9.0, `Bundle::Search.respond_to?(:across)`
133
- → `true`, `engine_for([:fuzzy])` → `OKF::Bundle::Search::Index`. RubyGems lists
134
- okf 1.9.0 as the current release.
135
- [2] Verified 2026-07-19 against the okf checkout: `engine_for([:fuzzy])` → `index`,
136
- `engine_for([])` → `scan`; `DEFAULT_ENGINE = :scan` in
137
- `lib/okf/bundle/search.rb`.
138
- [3] Measured 2026-08-13 against the registry's own five bundles (129 concepts):
139
- `across` per query 391.8 ms; with a held corpus 16.2 / 13.0 / 12.0 / 14.4 ms
140
- after the first. On the six test fixtures (36 concepts): 62–80 ms against
141
- 2.7–3.0 ms.
142
- [4] Reproduced 2026-07-18: `ruby -Ilib` outside bundler loaded okf 1.8.0 from the
143
- mise gem path, `Bundle::Search.respond_to?(:across)` → `false`, search
144
- returned 0 hits for a term the checkout finds. Under `bundle exec` the same
145
- query returned 1 hit ("orphan") and 11 ("registry").
146
- [5] `lib/okf/tui.rb` — `OKF::TUI.search_capable?`.
@@ -3,7 +3,17 @@ type: Decision
3
3
  title: The Undeclared Width Dependency
4
4
  description: Ui.width rests on unicode-display_width, which arrives through tty-box rather than the gemspec — accepted deliberately, and guarded by a test that fails loudly if it stops arriving.
5
5
  tags: [dependencies, rendering, terminal]
6
- timestamp: 2026-07-19
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-07-19
9
+ sources:
10
+ - id: "1"
11
+ title: "Sabotage run 2026-07-19: `Ui.width` reduced to `plain.length`, both checks failed as predicted, then restored — 38 runs green."
12
+ resource: "Sabotage run 2026-07-19: `Ui.width` reduced to `plain.length`, both checks failed as predicted, then restored — 38 runs green"
13
+ - title: "`lib/okf/tui/ui.rb` — the guarded require and the fallback."
14
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/lib/okf/tui/ui.rb
15
+ - title: "`test/integration/geometry_test.rb` — `WIDE_STATES` and the display-columns check."
16
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/test/integration/geometry_test.rb
7
17
  ---
8
18
 
9
19
  # Overview
@@ -62,12 +72,4 @@ terminal sheared. A test that degrades alongside the code it checks is not a
62
72
  guard.
63
73
 
64
74
  Both were verified by forcing the fallback: a row measured 87 columns in an
65
- 80-column terminal, and CJK measured 3 instead of 6.[1]
66
-
67
- # Citations
68
-
69
- [1] Sabotage run 2026-07-19: `Ui.width` reduced to `plain.length`, both checks
70
- failed as predicted, then restored — 38 runs green.
71
- [2] `lib/okf/tui/ui.rb` — the guarded require and the fallback.
72
- [3] `test/integration/geometry_test.rb` — `WIDE_STATES` and the
73
- display-columns check.
75
+ 80-column terminal, and CJK measured 3 instead of 6.[^1]
data/.okf/index.md CHANGED
@@ -1,13 +1,19 @@
1
1
  ---
2
- okf_version: "0.1"
2
+ okf_version: "0.2"
3
3
  ---
4
4
 
5
5
  # okf-tui knowledge bundle
6
6
 
7
- The non-obvious knowledge behind **okf-tui** — the full-screen terminal UI over
8
- [okf](@okf) bundles. The `README` documents what the six views answer and which
9
- keys drive them, and `AGENTS.md` carries the contracts a change has to keep; this
10
- bundle deliberately restates neither.
7
+ The knowledge behind **okf-tui** — the full-screen terminal UI over
8
+ okf (`@okf`) bundles. It is written to be read *before* opening `lib/`, so an
9
+ agent about to add a view, a key or a panel does not re-derive the layering and
10
+ does not rebuild something the kernel or this program already answers.
11
+
12
+ `AGENTS.md` beside it carries the contracts a change has to keep and routes here
13
+ for everything else; the `README` is the user's. What used to be a
14
+ hand-maintained Map of `lib/**` in `AGENTS.md` now lives in
15
+ [Structure](structure/), pinned by `test/unit/bundle_catalog_test.rb` — the code
16
+ is the truth and this bundle is the claim, so the two cannot drift quietly.
11
17
 
12
18
  What it captures is what the code cannot tell you on its own: *why* the
13
19
  interaction model is what it is (each piece of it arrived at by getting it wrong
@@ -22,6 +28,8 @@ turned out to be harder than it looked.
22
28
 
23
29
  # Areas
24
30
 
31
+ * [Structure](structure/) - Every file under `lib/`, grouped by the layer that owns it: the doors, the workspace and the model, the app, and the rendering layer.
32
+ * [Capabilities](capabilities/) - The catalogue: the six views and the kernel surface behind them, before you build a seventh.
25
33
  * [Decisions](decisions/) - The choices and their tradeoffs: the analysis boundary with okf, the search facade and the branch it outlived, the inherited Ruby floor, and why no dependency carries a ceiling.
26
34
  * [Interaction](interaction/) - The keyboard model — key routing and its modes, Esc as a stack, submitted rather than live search, following a link out of the page, and the two independent axes of "which bundle".
27
35
  * [Rendering](rendering/) - Composing a frame: ANSI-aware width, whole-frame painting, the tty-markdown trap that only appears in colour, and the one verdict a bundle wears everywhere.
@@ -3,7 +3,14 @@ type: Concept
3
3
  title: Active Bundle and Scope Are Two Axes
4
4
  description: What you are reading and what you are searching move independently, and the registry writes that reconcile them key on the directory rather than the slug.
5
5
  tags: [ux, search, registry]
6
- timestamp: 2026-07-19
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-07-19
9
+ sources:
10
+ - title: "`lib/okf/tui/workspace.rb` — `reload`, `add`, `remove`, `make_default`, `rename`."
11
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/lib/okf/tui/workspace.rb
12
+ - title: "`test/test_helper.rb` — `with_registry`; `test/integration/terminal_test.rb` — the unchanged-registry assertion."
13
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/test/test_helper.rb
7
14
  ---
8
15
 
9
16
  # Overview
@@ -52,10 +59,3 @@ says so rather than pretending to configure one.
52
59
  The suite never touches the real `~/.okf`: every test runs against a temporary
53
60
  `$OKF_HOME`, and the pty test asserts the registry file is byte-identical
54
61
  afterwards.
55
-
56
- # Citations
57
-
58
- [1] `lib/okf/tui/workspace.rb` — `reload`, `add`, `remove`, `make_default`,
59
- `rename`.
60
- [2] `test/test_helper.rb` — `with_registry`; `test/integration/terminal_test.rb`
61
- — the unchanged-registry assertion.
@@ -3,7 +3,14 @@ type: Decision
3
3
  title: Search Submits, It Does Not Follow Typing
4
4
  description: Enter runs the search rather than every keystroke, because a cross-bundle index is rebuilt per query — and the regression that hides is invisible on screen.
5
5
  tags: [ux, search, keys]
6
- timestamp: 2026-07-18
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-07-18
9
+ sources:
10
+ - title: "`lib/okf/tui/app.rb` — `@searched`, `@search_hits_key`."
11
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/lib/okf/tui/app.rb
12
+ - title: "`test/integration/search_test.rb` — the counting test."
13
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/test/integration/search_test.rb
7
14
  ---
8
15
 
9
16
  # Overview
@@ -42,8 +49,3 @@ identical bug. Both keys are now `.dup`ed.
42
49
 
43
50
  A cache keyed on a mutable string it does not own is not a cache; it is a
44
51
  one-shot.
45
-
46
- # Citations
47
-
48
- [1] `lib/okf/tui/app.rb` — `@searched`, `@search_hits_key`.
49
- [2] `test/integration/search_test.rb` — the counting test.
@@ -3,7 +3,15 @@ type: Decision
3
3
  title: Esc Peels One Layer
4
4
  description: Esc ends the innermost active thing and nothing else — the rule that stops it from resetting a list cursor and losing the file the reader had open.
5
5
  tags: [ux, keys, scar-tissue]
6
- timestamp: 2026-07-18
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-07-18
9
+ sources:
10
+ - id: "1"
11
+ title: "Reproduced 2026-07-18 in `test/integration/browse_test.rb`: with `overview` open the cursor moved 4 → 1 on Esc. The test was written red first and passed unedited after the fix."
12
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/test/integration/browse_test.rb
13
+ - title: "`lib/okf/tui/app.rb` — `handle_escape`."
14
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/lib/okf/tui/app.rb
7
15
  ---
8
16
 
9
17
  # The rule
@@ -31,7 +39,7 @@ still active while `@finding` is false.
31
39
 
32
40
  Esc from that state fell through to the list's own Esc, which clears the filter
33
41
  and calls `reset_cursor`. The reader was reading a concept and landed back on the
34
- **first file in the bundle**, having lost the one they had open.[1]
42
+ **first file in the bundle**, having lost the one they had open.[^1]
35
43
 
36
44
  ```ruby
37
45
  if findable? && !@find.empty?
@@ -61,10 +69,3 @@ the bug above: it is handled *inside* the picker's own key handler rather than i
61
69
  `handle_escape`, so closing it can never fall through to the layers beneath —
62
70
  which is exactly what the find failed to do. See
63
71
  [following-links](/interaction/following-links.md).
64
-
65
- # Citations
66
-
67
- [1] Reproduced 2026-07-18 in `test/integration/browse_test.rb`: with `overview`
68
- open the cursor moved 4 → 1 on Esc. The test was written red first and
69
- passed unedited after the fix.
70
- [2] `lib/okf/tui/app.rb` — `handle_escape`.
@@ -3,7 +3,18 @@ type: Concept
3
3
  title: A Dead Filter Offers the Wider Search
4
4
  description: Filtering reads metadata in one bundle and searching reads bodies across all of them, so a filter that matches nothing offers the search rather than leaving a dead end.
5
5
  tags: [ux, search]
6
- timestamp: 2026-08-13
6
+ generated:
7
+ by: human:maintainer
8
+ at: 2026-08-13
9
+ sources:
10
+ - title: "`lib/okf/tui/views.rb` — `escalation_panel`."
11
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/lib/okf/tui/views.rb
12
+ - title: "`lib/okf/tui/app.rb` — `filter_found_nothing?`, which is where the two views' conditions live side by side."
13
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/lib/okf/tui/app.rb
14
+ - title: "`test/integration/search_test.rb` — \"a filter that matches nothing offers the wider search\"."
15
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/test/integration/search_test.rb
16
+ - title: "`test/integration/groups_test.rb` — \"a filter matching a group but no bundle has found something\"."
17
+ resource: https://github.com/serradura/okf/blob/main/gems/okf-tui/test/integration/groups_test.rb
7
18
  ---
8
19
 
9
20
  # Overview
@@ -45,13 +56,3 @@ group and no bundle has found something, and the first cut of this read only the
45
56
  bundles pane — so `Enter` escalated on the keystroke that was accepting the
46
57
  filter, taking the filter, the view and the group the reader was pointing at with
47
58
  it. "The filter found nothing" is a claim about the whole view.
48
-
49
- # Citations
50
-
51
- [1] `lib/okf/tui/views.rb` — `escalation_panel`.
52
- [2] `lib/okf/tui/app.rb` — `filter_found_nothing?`, which is where the two views'
53
- conditions live side by side.
54
- [3] `test/integration/search_test.rb` — "a filter that matches nothing offers
55
- the wider search".
56
- [4] `test/integration/groups_test.rb` — "a filter matching a group but no bundle
57
- has found something".