okf-tui 1.0.0
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.
- checksums.yaml +7 -0
- data/.okf/decisions/index.md +12 -0
- data/.okf/decisions/invents-no-analysis.md +53 -0
- data/.okf/decisions/no-version-ceilings.md +69 -0
- data/.okf/decisions/okf-capability-drift.md +120 -0
- data/.okf/decisions/one-door-the-plugin-seam.md +131 -0
- data/.okf/decisions/registry-write-boundary.md +175 -0
- data/.okf/decisions/ruby-floor.md +59 -0
- data/.okf/decisions/search-facade-coupling.md +146 -0
- data/.okf/decisions/undeclared-width-dependency.md +73 -0
- data/.okf/index.md +28 -0
- data/.okf/interaction/cross-bundle-scope.md +61 -0
- data/.okf/interaction/deferred-search.md +49 -0
- data/.okf/interaction/esc-peels-one-layer.md +70 -0
- data/.okf/interaction/filter-escalates-to-search.md +57 -0
- data/.okf/interaction/following-links.md +82 -0
- data/.okf/interaction/index.md +12 -0
- data/.okf/interaction/key-routing.md +84 -0
- data/.okf/interaction/which-registry.md +85 -0
- data/.okf/log.md +38 -0
- data/.okf/rendering/ansi-aware-width.md +74 -0
- data/.okf/rendering/index.md +8 -0
- data/.okf/rendering/markdown-rendering-trap.md +63 -0
- data/.okf/rendering/status-vocabulary.md +45 -0
- data/.okf/rendering/whole-frame-painting.md +52 -0
- data/.okf/testing/ci-matrix.md +80 -0
- data/.okf/testing/headless-frames.md +74 -0
- data/.okf/testing/index.md +8 -0
- data/.okf/testing/pty-test.md +73 -0
- data/CHANGELOG.md +239 -0
- data/LICENSE.txt +201 -0
- data/NOTICE +10 -0
- data/README.md +194 -0
- data/lib/okf/plugin.rb +63 -0
- data/lib/okf/tui/app.rb +1908 -0
- data/lib/okf/tui/cli.rb +154 -0
- data/lib/okf/tui/model.rb +410 -0
- data/lib/okf/tui/refs.rb +63 -0
- data/lib/okf/tui/ui.rb +308 -0
- data/lib/okf/tui/version.rb +7 -0
- data/lib/okf/tui/views.rb +1648 -0
- data/lib/okf/tui/workspace.rb +527 -0
- data/lib/okf/tui.rb +76 -0
- metadata +229 -0
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2026-08-15
|
|
9
|
+
|
|
10
|
+
First release.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Six views over OKF bundles**: bundles, browse, search, graph, health, help.
|
|
15
|
+
It invents no analysis — okf owns the format, the model, and every question on
|
|
16
|
+
screen.
|
|
17
|
+
- **It targets OKF v0.2, and reads v0.1 as well as okf does.** The rule is okf's
|
|
18
|
+
own and it decides every §5 surface below: v0.2 only added optional keys, so a
|
|
19
|
+
bundle that adopted none of them must not read as deficient. A v0.1 bundle
|
|
20
|
+
reads as v0.1 — no empty columns, no rows saying "unverified" about a family it
|
|
21
|
+
never had — and §13.1 does the rest, so a v0.1 `timestamp:` shows as the date
|
|
22
|
+
it records, with no actor invented for it because none was ever recorded. Which spec a bundle is on is asked of the bundle (`Bundle#okf_version`)
|
|
23
|
+
rather than assumed: the health view names v0.1 or v0.2 as declared, and calls a
|
|
24
|
+
bundle that declares neither conformant rather than guessing at a number.
|
|
25
|
+
- **§5's provenance, on the concept you are reading.** The browse detail pane says
|
|
26
|
+
when a concept was generated and by whom, how many sources back it, when it
|
|
27
|
+
expires, and its status where that status is not the `stable` an absent one
|
|
28
|
+
already means. The trust tier is the one that needs a rule rather than a
|
|
29
|
+
presence check: §5.3 *derives* `unverified` for every concept that verified
|
|
30
|
+
nothing, so printing it unconditionally would paint a provenance verdict onto
|
|
31
|
+
documents that never made one. `Bundle::RowFilter.shows_trust?` is okf's
|
|
32
|
+
predicate — shared with its server and its graph page — and the graph facet
|
|
33
|
+
gates on the same call, so a tier is claimed in exactly one place or in none.
|
|
34
|
+
- **`status` and `trust` are the graph's fourth and fifth facets**, offered only
|
|
35
|
+
where the bundle has something to say — a status needs one *declared* value,
|
|
36
|
+
since a column of `stable` is what an undeclared status already means, and
|
|
37
|
+
trust needs one tier okf is willing to claim. Narrowing goes through
|
|
38
|
+
`Bundle::RowFilter.matches?`, so the two folds that make this non-trivial are
|
|
39
|
+
okf's rather than a copy: an absent status reads `stable` here exactly as it
|
|
40
|
+
does under `--status`, and a tier folds both spellings. The count and the
|
|
41
|
+
narrowing read one predicate, so a facet selects precisely the rows it counted.
|
|
42
|
+
- **Browse reads a bundle in spec order** — `index.md`, `log.md`, then each
|
|
43
|
+
directory — and renders concept bodies as markdown. `/` looks through whatever
|
|
44
|
+
has focus: a list filters, a document finds.
|
|
45
|
+
- **Follow a markdown link out of the document you are reading**: `f` lists what
|
|
46
|
+
it points at, `1`–`9` or `Enter` goes there, `Esc` puts the body back. The list
|
|
47
|
+
comes from okf's own link extraction — the same one the graph builds edges
|
|
48
|
+
with — so a concept reads as its title and a dead link as not written yet.
|
|
49
|
+
Directory links (`[Decisions](decisions/)`, how every index points at its area)
|
|
50
|
+
follow to that directory's `index.md`.
|
|
51
|
+
- **`Backspace` returns to wherever a jump started.** Shared by every jump, so
|
|
52
|
+
opening a search hit and following a concept out of the graph are reversible
|
|
53
|
+
too.
|
|
54
|
+
- **Search spans every bundle in scope through one shared index**, so the scores
|
|
55
|
+
compare between them; submitted with `Enter` rather than run per keystroke. The
|
|
56
|
+
corpus is built once and held — measured over five registered bundles, 129
|
|
57
|
+
concepts between them: **392 ms** for the first query, then **12–16 ms** for
|
|
58
|
+
every one after. It is built on first use rather than at load — a session that
|
|
59
|
+
never searches should not pay to index bundles nobody opened — keyed on the
|
|
60
|
+
scope, and dropped on any reload, because a held index outliving the set it was
|
|
61
|
+
built from is a wrong answer rather than a slow one.
|
|
62
|
+
- **`e` chooses how a query is asked** — `fuzzy`, `text` or `regexp`. okf's index
|
|
63
|
+
and scan disagree *by design*, so offering only the index would leave every term
|
|
64
|
+
glued to a symbol unfindable with nothing on screen saying so. Measured on okf's
|
|
65
|
+
own bundle: the index finds three of the five concepts that say `minifts` (a
|
|
66
|
+
backtick is not punctuation, so a word in a code span indexes as
|
|
67
|
+
`` `minifts` ``), and returns fourteen results for `OKF_HOME` where the scan
|
|
68
|
+
returns five, because it splits the term and matches the halves. `text` is okf's
|
|
69
|
+
own CLI default and has no tokenizer to get in the way; `regexp` is a capability
|
|
70
|
+
only the scan declares. The mode is named on screen beside the query rather than
|
|
71
|
+
living only in a keystroke, the hits are keyed on it so switching re-asks rather
|
|
72
|
+
than relabelling the previous engine's answer, and an unparseable pattern reports
|
|
73
|
+
itself instead of being rescued into "no matches" — which is indistinguishable
|
|
74
|
+
from a term that is genuinely absent. One held corpus serves all three; the scan
|
|
75
|
+
declares no `prepare`, so it reuses the documents rather than an index.
|
|
76
|
+
- **A registry filter that matches nothing offers the search**, exactly as browse
|
|
77
|
+
does: `Enter` takes the term to view 3 and searches every bundle for it. A filter
|
|
78
|
+
over a dozen slugs is a narrow thing to be typing, and a term matching none of
|
|
79
|
+
them is usually a question about what the bundles *say*. Both panes have to be
|
|
80
|
+
empty, so a filter naming a group still accepts on `Enter`.
|
|
81
|
+
- **The graph is navigable**: pick a type or tag to narrow it, or a concept to
|
|
82
|
+
open it. A concept whose type is blank or absent is named `Untyped` rather than
|
|
83
|
+
hidden — §9.2 requires a type, which makes those concepts exactly what a curator
|
|
84
|
+
is looking for, and okf's own graph index labels them the same way.
|
|
85
|
+
- **`dir` is a third facet in the graph view.** okf 1.11.0 made the full directory
|
|
86
|
+
path a filter on six verbs and 1.12.0 deprecated the first-segment `--area` for
|
|
87
|
+
"losing every level below it". Selecting a directory reaches everything beneath
|
|
88
|
+
it, exactly as `--dir` does, and the counts are the subtree counts `okf dirs`
|
|
89
|
+
prints. Offered only where the bundle actually nests: at one level deep every dir
|
|
90
|
+
facet says what a top-level rollup says, which is what `--area` was deprecated
|
|
91
|
+
for being.
|
|
92
|
+
- **Each bundle's standing** (conformance, curation) colours it wherever it is
|
|
93
|
+
named, and flags the health tab.
|
|
94
|
+
- **Health is two panes: findings on the left, standing on the right.** One page
|
|
95
|
+
mixing a bounded summary with an unbounded findings list, list on top, drops the
|
|
96
|
+
dir traffic and the stats below the fold — and drops them further the more
|
|
97
|
+
findings a bundle has, which is exactly the bundle whose structure you opened the
|
|
98
|
+
view to read. The summary cannot be pushed away, and the two scroll apart: `Tab`
|
|
99
|
+
names which one the keys move. The right pane carries verdicts and numbers and
|
|
100
|
+
never a path, which is what lets it hold a fixed width; the errors behind its
|
|
101
|
+
counts are listed on the left, where the columns are. Below 112 columns the two
|
|
102
|
+
would each be too narrow for the paths a finding is *about*, so the same `Tab`
|
|
103
|
+
shows one at a time instead.
|
|
104
|
+
- **Health says which checks did not run.** §5.5's freshness pair is clock-gated,
|
|
105
|
+
and the pure library runs neither unless handed one — so lint reports
|
|
106
|
+
`expired, stale not run — no clock supplied` rather than letting "lint clean"
|
|
107
|
+
stand for a verdict it did not earn. A check that quietly sat out is the one way
|
|
108
|
+
a health screen is worse than no health screen.
|
|
109
|
+
- **The bundle's §5 posture, beside its verdicts.** `trust unverified 31` and
|
|
110
|
+
`status stable 22 · deprecated 1` — okf's own lint numbers, unedited, on the
|
|
111
|
+
pane built for numbers. Shown only where the bundle declared something, on the
|
|
112
|
+
same gate the facets use, and a tier the bundle has none of is dropped rather
|
|
113
|
+
than clipped: this pane holds a fixed width because its rows are short by
|
|
114
|
+
construction, and all three tiers with their counts is not.
|
|
115
|
+
- **Hubs and directory traffic, on the health view.** `Bundle#hubs` ranks concepts
|
|
116
|
+
by inbound links and says which directories those links come from — the evidence
|
|
117
|
+
for "is this hub well homed?" A hub drawing its majority from outside its own
|
|
118
|
+
top-level dir is flagged, and where a single foreign dir carries that majority it
|
|
119
|
+
is named, because that is the better home the concept has already found.
|
|
120
|
+
`Bundle#skeleton` adds the same question one grain coarser: each directory's
|
|
121
|
+
internal, outbound and inbound traffic, with the internal share as a
|
|
122
|
+
**cohesion**, sorted so the directories with a case to answer come first. A
|
|
123
|
+
directory with no traffic at all reads `—` rather than a `0%` it did not earn.
|
|
124
|
+
Evidence, not verdicts: okf is explicit that near-zero cohesion under heavy
|
|
125
|
+
inbound can be a shared vocabulary doing its job.
|
|
126
|
+
- **The directory arcs, under the cohesion table.** The table says how much of a
|
|
127
|
+
directory's traffic stays home; the arcs say where the rest of it goes, which is
|
|
128
|
+
the other half of `graph --traffic` and the half that names a pair. Narrowed to
|
|
129
|
+
the cut okf *fits to the bundle* — asked for rather than guessed, since okf
|
|
130
|
+
measured ten bundles at a fixed weight and got anywhere from 2 arcs to 136 — and
|
|
131
|
+
the row says how many of how many survived it, because a silently shortened list
|
|
132
|
+
reads as a complete one. Cohesion is still computed over every arc, which is
|
|
133
|
+
okf's rule and is asserted, so narrowing the picture never moves the evidence.
|
|
134
|
+
- **Registry configuration in place** — `a` registers a bundle, `x` removes one,
|
|
135
|
+
`d` sets the default, `n` renames. Every write goes through okf's `Registry` and
|
|
136
|
+
is followed by a reload, so the screen shows what the file now says rather than
|
|
137
|
+
what memory believes. `okf registry init` is deliberately not offered: the
|
|
138
|
+
registry is resolved once at boot, so creating one mid-session would swap the
|
|
139
|
+
whole workspace out from under every open view rather than edit the one it is on.
|
|
140
|
+
- **Registry groups are on screen, and one key scopes a search to one.** A group
|
|
141
|
+
is a named, recursive set of bundles — which is to say a named search scope, and
|
|
142
|
+
the scope is what this view already manages. Groups list under their own heading
|
|
143
|
+
with the bundles they resolve to; `Enter` makes a search cover exactly those, the
|
|
144
|
+
same set `okf search @group` merges into one ranking. The detail pane shows the
|
|
145
|
+
members as the registry records them *and* the bundles they resolve to, because
|
|
146
|
+
for a nested group those differ. A member naming nothing registered is called out
|
|
147
|
+
rather than dropped, and a hand-edited cycle — which okf refuses to create and
|
|
148
|
+
reports by declining to resolve — says so and refuses to be scoped rather than
|
|
149
|
+
silently covering nothing.
|
|
150
|
+
- **Groups can be built and edited here too**, which takes the TUI to seven of
|
|
151
|
+
okf's eight `registry` verbs. `c` names the bundles now in scope as a new group,
|
|
152
|
+
because `◉` already means "these bundles" in this view; `+` adds, `-` removes,
|
|
153
|
+
`n` renames and `x` deletes. okf owns every cascade — a rename reaches every
|
|
154
|
+
member list that named the group, a delete drops it from all of them, and a group
|
|
155
|
+
left with no members is deleted.
|
|
156
|
+
|
|
157
|
+
**The bundles view is three panes** — the registry's bundles, its groups, and the
|
|
158
|
+
detail of whichever has focus — with `Tab` cycling and `Esc` stepping back out
|
|
159
|
+
one pane at a time. A heading inside the bundle list scrolls away: thirteen
|
|
160
|
+
registered bundles put the groups below the fold on a short terminal, which is no
|
|
161
|
+
way to show something you are meant to select.
|
|
162
|
+
|
|
163
|
+
Two panes also mean two selections at once, and that is what makes the editing
|
|
164
|
+
keys direct. Each acts on a row that is on screen: `+` in the bundles pane adds
|
|
165
|
+
the bundle under that cursor to the group selected below, `-` in the members pane
|
|
166
|
+
removes the member under that cursor, and `n`/`x` in the groups pane rename or
|
|
167
|
+
delete the selected group. Nothing consults the search scope except `c`, where
|
|
168
|
+
naming the bundles you have been searching together is the point. The groups pane
|
|
169
|
+
keeps its cursor while the bundles have focus, dimmed, so the row `+` acts on is
|
|
170
|
+
one the reader can point at — and the footer deliberately does not spell that
|
|
171
|
+
group's slug out, since naming an off-screen target claims a selection nothing on
|
|
172
|
+
screen agrees with.
|
|
173
|
+
|
|
174
|
+
The removal key reads a row for that reason above all. A `-` acting on the
|
|
175
|
+
*intersection* of the scope and the member list would act on a set with no row
|
|
176
|
+
on screen, so the key could not be predicted without computing it in your head,
|
|
177
|
+
and its most obvious misreading ("turn the scope off") would take a whole group
|
|
178
|
+
with it. Anything that can lose configuration asks first and names the
|
|
179
|
+
consequence — `x`,
|
|
180
|
+
and `-`, which says when the member being removed is the last one and the group
|
|
181
|
+
goes with it. `+`, `c` and `d` add or reorder, and do not ask.
|
|
182
|
+
|
|
183
|
+
A `+` has to change something visible, and what it changes is not the row: the
|
|
184
|
+
bundle detail pane lists every group that names the bundle — `in @docs
|
|
185
|
+
@everything` — and the status line names both sides of the write,
|
|
186
|
+
`@minimal joined @docs — 3 bundles`. An edit to a group that *is* the scope in
|
|
187
|
+
force re-applies that scope, in both directions, so set equality keeps holding
|
|
188
|
+
and the bundle just added does not read as out of scope on its own row. A group
|
|
189
|
+
nobody scoped is left alone, since re-scoping on every edit would replace a
|
|
190
|
+
hand-made selection.
|
|
191
|
+
- **The status row is either asking you something or telling you something**, and
|
|
192
|
+
it wears yellow for the first and cyan for the second — one colour each tells the
|
|
193
|
+
two apart before a word is read. A flash can afford the mark because the next
|
|
194
|
+
keystroke clears it along with the message, so it cannot become permanent noise.
|
|
195
|
+
- **Quitting is `q q`.** A single `q` ends a session on one stray keystroke; the
|
|
196
|
+
first press arms and says so, and any other key disarms it. `Ctrl-c` still quits
|
|
197
|
+
outright.
|
|
198
|
+
- **`okf tui` is the entry point, and the only one — this gem ships no
|
|
199
|
+
executable.** okf dispatches through a command registry and finds extensions by
|
|
200
|
+
convention (any gem with `okf/plugin.rb` on its load path can register a verb),
|
|
201
|
+
so installing this gem is the whole installation: no configuration, and `okf
|
|
202
|
+
help` lists `tui` under `installed extensions:`. There is no second binary: one
|
|
203
|
+
that only aliased the verb would be one more name to install, document and keep
|
|
204
|
+
working, and two front ends are two argument grammars waiting to drift, with
|
|
205
|
+
each one passing its own tests while they disagree. What ships instead is one
|
|
206
|
+
adapter that carries argv and the streams and adds nothing else, which a test
|
|
207
|
+
pins by running the same invocation both ways and comparing the message.
|
|
208
|
+
- **Every ref form okf's other multi-bundle verb takes**:
|
|
209
|
+
`@slug`, bare `@` for the registry default, `@group` fanned out to its members, a
|
|
210
|
+
vanished member skipped with a note, and `@all` refused by name as search's
|
|
211
|
+
alone. The grammar is *inherited* rather than copied — `OKF::TUI::Refs`
|
|
212
|
+
subclasses `OKF::CLI::Command` for okf's own resolver — so there is one copy of
|
|
213
|
+
it, and a test pins the seam so that okf moving it fails loudly instead of
|
|
214
|
+
quietly restoring "not a directory".
|
|
215
|
+
- **A project-local `.okf-registry.json` resolves ahead of the global one**, the
|
|
216
|
+
same as every other okf verb, discovered by walking up from the working
|
|
217
|
+
directory; `OKF_NO_DISCOVERY=1` opts out and `$OKF_HOME` names the global one.
|
|
218
|
+
Reloads go through `Registry#reopen` so a local registry's relative paths keep
|
|
219
|
+
the anchor they are stored against. The library keyword mirrors okf's own rule:
|
|
220
|
+
`Workspace.new(cwd:)` opts in, and an embedding app that passes no cwd stays
|
|
221
|
+
global-only.
|
|
222
|
+
|
|
223
|
+
### Requires
|
|
224
|
+
|
|
225
|
+
- **Ruby >= 2.4**, the same floor as okf.
|
|
226
|
+
- **`okf >= 2.0, < 3`** — the kernel release that ships OKF v0.2. A real floor and
|
|
227
|
+
not a formality: eleven okf capabilities are load-bearing here, and the gemspec
|
|
228
|
+
lists which and why. Most of them fail *silently* against an older okf — a
|
|
229
|
+
renamed field reads as nil, a missing method is rescued into "no matches" —
|
|
230
|
+
which is why the floor is stated rather than left to chance, and why the boot
|
|
231
|
+
checks stay: `OKF::TUI.search_capable?` and `.spec_capable?` refuse to start and
|
|
232
|
+
name the okf that answered, because resolution cannot stop a second one sitting
|
|
233
|
+
ahead of the intended one on the load path. The ceiling is earned rather than
|
|
234
|
+
conventional, and by the same evidence: an okf *major* is where the silent drift
|
|
235
|
+
comes from (`area` → `top_dir`, then `timestamp` → `generated_at`), so `< 3`
|
|
236
|
+
turns the next one into a resolution failure a maintainer sees instead of a
|
|
237
|
+
wrong number a reader believes.
|
|
238
|
+
|
|
239
|
+
[1.0.0]: https://github.com/serradura/okf-gem/releases/tag/okf-tui/v1.0.0
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 Rodrigo Serradura
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
data/NOTICE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
OKF gem
|
|
2
|
+
Copyright (c) 2026 Rodrigo Serradura
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (see LICENSE.txt).
|
|
5
|
+
|
|
6
|
+
This product bundles the Open Knowledge Format (OKF) v0.1 specification
|
|
7
|
+
(lib/okf/skill/reference/SPEC.md), authored by Google Cloud Platform and
|
|
8
|
+
licensed under the Apache License, Version 2.0, Copyright (c) Google LLC.
|
|
9
|
+
Source: https://github.com/GoogleCloudPlatform/knowledge-catalog
|
|
10
|
+
Full license text: lib/okf/skill/reference/APACHE-2.0.txt
|
data/README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# okf-tui
|
|
2
|
+
|
|
3
|
+
A full-screen terminal UI for [Open Knowledge Format](https://github.com/serradura/okf-gem)
|
|
4
|
+
bundles: read one, switch between many, configure the registry, and search
|
|
5
|
+
across all of them at once. Built on the [TTY toolkit](https://ttytoolkit.org/components/).
|
|
6
|
+
|
|
7
|
+
Installing it teaches `okf` a verb. There is no second command to learn and
|
|
8
|
+
nothing else to set up:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
gem install okf-tui
|
|
12
|
+
okf tui # every bundle in your registry
|
|
13
|
+
okf tui path/to/bundle ... # those bundles, ad-hoc — the registry is left alone
|
|
14
|
+
okf tui @handbook # a registered bundle; bare @ is the default
|
|
15
|
+
okf tui @backend # a registry group, fanned out to its members
|
|
16
|
+
OKF_HOME=/tmp/scratch okf tui # a different global registry
|
|
17
|
+
okf help # lists `tui` under "installed extensions:"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This gem ships **no executable of its own** — deliberately. A second binary that
|
|
21
|
+
only aliased `okf tui` would be one more name to install, document and keep
|
|
22
|
+
working, and two front ends is two argument grammars waiting to drift. `okf`
|
|
23
|
+
finds this gem because it ships `okf/plugin.rb`, the convention any gem can use
|
|
24
|
+
to add a verb — see [extension points](https://github.com/serradura/okf-gem/blob/main/.okf/design/extension-points.md).
|
|
25
|
+
|
|
26
|
+
The argument shape mirrors `okf server` exactly, because it is the same grammar:
|
|
27
|
+
`@slug` names a registered bundle, bare `@` the registry default, `@group` fans out
|
|
28
|
+
to its members, and `@all` is refused as `okf search`'s alone. Naming directories
|
|
29
|
+
is an ad-hoc look at them and never enrols them in the registry — registering stays
|
|
30
|
+
an explicit act, here the `a` key.
|
|
31
|
+
|
|
32
|
+
## Six views
|
|
33
|
+
|
|
34
|
+
| View | Answers |
|
|
35
|
+
|------|---------|
|
|
36
|
+
| **1 bundles** | What can I open, which is active, which is the default — the registry's groups, and all registry config |
|
|
37
|
+
| **2 browse** | What is in the bundle, in reading order — `index.md`, `log.md`, then each directory |
|
|
38
|
+
| **3 search** | Which concept covers X — across *every bundle in scope*, ranked together, in whichever of okf's engines answers it |
|
|
39
|
+
| **4 graph** | What shape is its knowledge — narrow by type, tag or dir, or pick a concept to go read |
|
|
40
|
+
| **5 health** | Is it legal, well curated, and well structured — findings on the left (`validate`, `lint`, hubs), standing on the right (dir cohesion, the arcs between dirs, stats) |
|
|
41
|
+
| **6 help** | The keys |
|
|
42
|
+
|
|
43
|
+
Browse is where okf's §6 map lands too: `/index` narrows the list to the way in
|
|
44
|
+
each directory authored, one row per directory, and opens the authored file.
|
|
45
|
+
|
|
46
|
+
## Two axes, kept apart
|
|
47
|
+
|
|
48
|
+
"Which bundle" is really two questions, and they move independently:
|
|
49
|
+
|
|
50
|
+
- the **active bundle** (`●`) — what browse, graph and health are about. `Enter`
|
|
51
|
+
on a bundle changes it.
|
|
52
|
+
- the **scope** (`◉`) — which bundles a search covers. `space` toggles one, `A`
|
|
53
|
+
all, `N` none, and `Enter` on a group makes it exactly that group's bundles.
|
|
54
|
+
`N` is how you start a fresh selection.
|
|
55
|
+
|
|
56
|
+
So you can read one bundle while searching all of them. Opening a search hit
|
|
57
|
+
that lives in a *different* bundle switches the active bundle to it.
|
|
58
|
+
|
|
59
|
+
The scope is also what the search corpus is built over, and it is built **once** and
|
|
60
|
+
held — the first query pays for it, the rest are effectively free. Changing the
|
|
61
|
+
scope or reloading drops it, because an index that outlives the set it was built
|
|
62
|
+
from gives a wrong answer rather than a slow one.
|
|
63
|
+
|
|
64
|
+
`e` chooses how the query is asked, because okf's two engines disagree by design
|
|
65
|
+
and each is wrong for what the other is right for:
|
|
66
|
+
|
|
67
|
+
| Mode | Engine | Good for |
|
|
68
|
+
|------|--------|----------|
|
|
69
|
+
| `fuzzy` | full-text index | ranked results, forgives typos |
|
|
70
|
+
| `text` | raw scan (okf's own default) | `$OKF_HOME`, `` `minifts` ``, `7.2.0` — terms the index tokenizer splits apart |
|
|
71
|
+
| `regexp` | raw scan | a pattern over the same text |
|
|
72
|
+
|
|
73
|
+
An unparseable pattern says so rather than quietly returning nothing.
|
|
74
|
+
|
|
75
|
+
Search runs **one index over every scoped bundle**, which is what makes the
|
|
76
|
+
scores comparable between them rather than only within one — the same thing
|
|
77
|
+
`okf search @all` does.
|
|
78
|
+
|
|
79
|
+
## `/` looks through whatever has focus
|
|
80
|
+
|
|
81
|
+
One key, whichever thing the cursor is in:
|
|
82
|
+
|
|
83
|
+
- **a list** — filters it. Bundles by slug or path, browse by title, id, type or
|
|
84
|
+
tag, graph by type, tag or concept id.
|
|
85
|
+
- **a document** — the concept body, and the health and help pages — finds
|
|
86
|
+
within it and scrolls to the hit, with `n` / `N` stepping through the matches.
|
|
87
|
+
|
|
88
|
+
Nothing grabs the field on arrival, so `1`–`6` mean the same thing everywhere.
|
|
89
|
+
In search, `Enter` submits the query and `Esc` stops editing without leaving the
|
|
90
|
+
view.
|
|
91
|
+
|
|
92
|
+
When a browse filter matches nothing, `Enter` takes the term to the search view
|
|
93
|
+
and runs it across every bundle — the filter reads metadata in one bundle, and
|
|
94
|
+
search reads bodies across all of them.
|
|
95
|
+
|
|
96
|
+
## `f` follows a link out of the page
|
|
97
|
+
|
|
98
|
+
A bundle is a graph, and its markdown links are the edges. `f` lists the ones
|
|
99
|
+
leaving the document you are reading — pick with `↑` `↓` and `Enter`, or press
|
|
100
|
+
`1`–`9` to go straight there. `Esc` puts the body back where you left it, and
|
|
101
|
+
`Backspace` returns you to wherever you jumped from.
|
|
102
|
+
|
|
103
|
+
It reads as what is at the far end, not as the path it was written with: a
|
|
104
|
+
concept by its title, a nested `index.md` by its area, and a link with nothing
|
|
105
|
+
behind it as knowledge not written yet. That last one is `lint`'s to report, not
|
|
106
|
+
an error to raise.
|
|
107
|
+
|
|
108
|
+
The reserved files are where this earns its keep — an `index.md` is a list of
|
|
109
|
+
links by design (§6) and `log.md` is a list of what changed where, so both become
|
|
110
|
+
menus into the bundle. The trail is shared, so opening a search hit or following
|
|
111
|
+
a concept out of the graph is undone by the same key.
|
|
112
|
+
|
|
113
|
+
## The bundle wears its verdict
|
|
114
|
+
|
|
115
|
+
A bundle is clean, has lint warnings, or is not conformant, and that one
|
|
116
|
+
judgement drives its colour everywhere it is named — the header, the footer
|
|
117
|
+
badge, its row in the registry, its detail pane. The **health tab carries it
|
|
118
|
+
too**, so a problem is visible from any view without opening the tab.
|
|
119
|
+
|
|
120
|
+
## Registry config
|
|
121
|
+
|
|
122
|
+
In the bundles view: `d` default, `a` add, `n` rename, `x` remove (asks first),
|
|
123
|
+
`r` reload. Each writes the registry file and re-reads from disk, so the screen
|
|
124
|
+
shows what the file now says. Removing a bundle never touches the bundle on
|
|
125
|
+
disk — the registry is a list of references.
|
|
126
|
+
|
|
127
|
+
It edits your *configuration*, never your knowledge: there is no bundle writing
|
|
128
|
+
anywhere in this gem. Authoring belongs to `okf` and its companion skill.
|
|
129
|
+
|
|
130
|
+
**Groups.** A registry group is a named, recursive set of bundles — which is to
|
|
131
|
+
say a named search scope. Groups list under their own heading, and `Enter` on one
|
|
132
|
+
makes a search cover exactly its bundles: the same set `okf search @backend`
|
|
133
|
+
merges into one ranking. The detail pane shows both the members the registry
|
|
134
|
+
records and the bundles they resolve to, since for a nested group those differ.
|
|
135
|
+
|
|
136
|
+
**The view has three panes**, and `Tab` cycles them: the registry's bundles, its
|
|
137
|
+
groups, and the detail of whichever of the two has focus. `Esc` steps back out one
|
|
138
|
+
pane at a time. Two panes means two selections at once, and that is what makes
|
|
139
|
+
editing direct — every key acts on a row you can see:
|
|
140
|
+
|
|
141
|
+
| Key | Pane | Does |
|
|
142
|
+
|-----|------|------|
|
|
143
|
+
| `Enter` | groups | make a search cover exactly this group's bundles |
|
|
144
|
+
| `+` | bundles | the bundle under this cursor joins the group selected below |
|
|
145
|
+
| `-` | members | remove the member under this cursor |
|
|
146
|
+
| `n` / `x` | groups | rename or delete the group; okf cascades through every member list |
|
|
147
|
+
| `c` | bundles | name the bundles now in scope as a new group |
|
|
148
|
+
|
|
149
|
+
Anything that can lose configuration asks first and names what it is about to do —
|
|
150
|
+
`x`, and `-`, which says when the member you are removing is the last one and the
|
|
151
|
+
group goes with it. `+`, `c` and `d` add or reorder, and do not ask.
|
|
152
|
+
|
|
153
|
+
**Which registry.** The same one every other `okf` verb run from the same
|
|
154
|
+
directory resolves: a project-local `.okf-registry.json` when one is on the path
|
|
155
|
+
up from here, and `$OKF_HOME` (default `~/.okf`) otherwise. `OKF_NO_DISCOVERY=1`
|
|
156
|
+
forces the global one. The header names the file it read, so there is never a
|
|
157
|
+
question which is in force. A workspace of directories named on the command line
|
|
158
|
+
has no registry, and says so rather than pretending to configure one.
|
|
159
|
+
|
|
160
|
+
## It invents no analysis
|
|
161
|
+
|
|
162
|
+
`OKF::Bundle::Reader` and `OKF::Registry` are the only parts that touch disk;
|
|
163
|
+
everything on screen is a pure call on the resulting in-memory bundles —
|
|
164
|
+
`catalog`, `graph`, `validate`, `lint`, `directories`, `hubs`, `skeleton`,
|
|
165
|
+
`Bundle::Search`. The TUI is one more shell over the same core the `okf` CLI and
|
|
166
|
+
the graph server already use.
|
|
167
|
+
|
|
168
|
+
That is a rule with teeth, not a slogan: the dir facet's counts are checked in CI
|
|
169
|
+
against `okf dirs --json`, and the cohesion table against `okf graph --traffic`,
|
|
170
|
+
row for row. If okf and this disagree about a number, the suite fails.
|
|
171
|
+
|
|
172
|
+
It keeps okf's contracts visible too: `validate` and `lint` are separate
|
|
173
|
+
sections because they answer different questions, reads are best-effort so an
|
|
174
|
+
unreadable file is reported (`⊘ n unreadable`) rather than fatal, and a bad
|
|
175
|
+
directory exits `2`, the usage-error code.
|
|
176
|
+
|
|
177
|
+
## Requirements
|
|
178
|
+
|
|
179
|
+
Ruby >= 2.4 — the same floor as okf, which takes it from rack: the tool should
|
|
180
|
+
run on whatever Ruby the OS already ships. `okf >= 2.0` is the other requirement,
|
|
181
|
+
and installing this gem pulls it in: every screen here answers with something the
|
|
182
|
+
kernel's OKF v0.2 work published, and against an older one each would fail
|
|
183
|
+
silently rather than loudly.
|
|
184
|
+
|
|
185
|
+
## Development
|
|
186
|
+
|
|
187
|
+
`bin/setup` to install, `bundle exec rake` to run the tests and RuboCop.
|
|
188
|
+
`bundle exec okf tui` runs it from the checkout — okf is a bundle dependency, so
|
|
189
|
+
the verb resolves the same way it does for a user. See [AGENTS.md](AGENTS.md)
|
|
190
|
+
for the contracts a change has to keep.
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
Apache-2.0.
|