Repository navigation
Releases: ofamtools/ochazuke
Release list
v0.17.0
- Framework namespace is
ochazuke: framework nouns areochazuke.settings,ochazuke.page,ochazuke.stateand so on. In v0.16.0 they wereapp.<noun>, which collided with theappnoun's own verbs (app.state). The short names stay as aliases, andapp.<framework noun>is accepted silently for one release. Exported noun constants changed value. cli.ExtensionNamespace(bin): strips a leadingo-and refuses reserved names (o-ai →ai, o-qbt →qbt). Hosts use it withcli.Namespace.- Catalog fix:
cli.Cataloglists each noun once, sorted by name. It used to split one noun into several entries. - Card timeline:
AddTimelinedraws boxed cards with wrapped headers (no…), lazily drawn bodies with foldable sections,Active(bold,●) andMutedcards, group rules, and follow-tail scrolling.
v0.16.0
- Noun namespaces:
- Framework nouns are now
app.<noun>(app.settings,app.config, …), and their short names stay as aliases. Defineextends a framework noun with the same short name;DefineOwn(name)lets an app claim the short name instead.- Dotted nouns split on the last dot (
SplitKey); a verb can't contain a dot. - The CLI takes nested, dotted and alias spellings (
bin app settings list,bin app.settings list,bin settings list). - Extension hosts namespace extension nouns with
cli.Namespace(ext). The catalog publishesaliases.
- Framework nouns are now
- Compatibility:
Runtime.NounsandRuntime.Operationsare keyed by canonical names, so look framework nouns up throughNoun()/Operation(). Several exported noun constants changed value. - State explorer:
ochazuke/explorepresses keys through any app built headless with fakes, and checks invariants in every state:- no panic, focus and modal rules, honest hints, no traps, render fits, jobs settle.
- Findings come with shrunk key paths, as gap stories or Go tests.
- Entry points:
explore.Run,Survey,Reproduce;o-story explore;make explore.
v0.15.0
config.reload: reads themes.json, settings.json, actions.json and bindings.json again, in dependency order, then each app hook (Runtime.OnConfigReload(name, fn)). Problems are reported per file and stale notices withdrawn.- Noun places: each instance's noun, ancestor-noun chain, page and modal are recorded and persisted per app in
noun-places.json. Read them withnouns places,nouns listandRuntime.NounPlaces. ?help views:Scycles here → pick → table. Pick shows a noun and mode you choose (n,m); table shows keys by mode. Both resolve at a recorded place (p), defaulting to where?was opened. Settingshelp.pick-view,help.table-viewandhelp.pending-modes.ctrl+\\opens the help from any insert mode.- Bindings export: rows print as mode, noun, request (verb first), keys.
bindings list --allandbindings.reference.jsonare sorted the same way.
v0.14.0
- Modals: about 80% of the terminal width with no 78-column cap; a chooser fills the modal width and no longer resizes while typing (story
chooser/wide-fills). - Terminal overlays:
Terminal.SetOverlay(*TerminalOverlay{Dim, Marks})draws over a live pane without touching its frame or stream. With an app passthrough mode entered from the leader, this gives tuios-style hints (storiesterminal/hints-*). - Keys: the key classes
upperandctrl+alpha, and the argument sourceletter. - Palette:
Parameter.Nounlists that noun's things as the parameter's choices.
v0.13.0
Table cell mode is opt-in: CellSelect(); every other table selects rows as before v0.12.0.
Table cell mode is opt-in: only a table added with CellSelect() is cell-based.
Tables
CellSelect()(anInstanceOptionforAddList) makes a table cell-based: the current column,h/l,0/^/home,$/end,N|,g c,y(copy the cell) andz >/z </z =, with the column and widths remembered intables.json, asv0.12.0gave every table.AddTreeignores it.- Every other table selects rows again, as on
v0.11.0:h/lare free for row nouns,$,^and0(without a count) runlist.scroll-columns(verb guardtable-scrolls),z l/z hscroll a column, no current cell is drawn, and nothing is read from or written totables.jsonfor it (widths av0.12.0cell table saved no longer stick to it). Storytables/row-select. RowSelect()is a deprecated no-op; the framework's trees,?help modal and Settings page no longer pass it.Y(copy the row's cells joined by tabs) answers on every table (verb guardlist-table),yonly on a cell-mode table.Vanswers on every table with marks: a visual range over the marks, cell-mode or not.escends it throughlist.end-visual(verb and row guardlist-visual), so the row no longer hides an outereschint, such as a modal'sesc modal.pop, over a table.- The width keys
z >/z </z =need a current column, so they answer on cell-mode tables only;List.SetColumnWidthstill sets a width on any table. list.column,list.choose-columnandlist.resize-columnrefuse a row-select table ("not a cell table");list.yank {what: cell}too.- cli: catalog tables select rows, so the groups rail's
hback row (guardlist-first-column) answers at once on them. Storycatalog/groups-table-h.
o-story
- A story list takes
cellSelect(ListSpec.CellSelect); the cell stories set it, and the goldens of tables that select rows readR|again.
v0.12.0
Tables are cell-based: a current column, per-column widths, and V for rows.
Tables are cell-based, like o-csv's grid: a current column, column widths, and a visual row selection.
Tables
- A list with columns has a current column (
List.Column,SetColumn;columnin its state). On thelistnoun in normal mode, on a cell-mode table only:h/l/left/rightmove by column (counts),0/^/homeand$/endgo to the first and last,N|to column N,g cchooses one from a chooser of the titles (list.column,list.choose-column, motions);j/kkeep the column. Every frame scrolls the column offset as little as shows the current column;z l/z hcarry it along.$,^and0no longer runlist.scroll-columns;list.columnscrolls a row-select table the same way. Storiestables/cell-motion,tables/column-count. - The current cell is drawn reverse and underlined inside the row's highlight, the header underlines the current column's title, and an unfocused table fades both. Story
tables/unfocused-cell-dimmed. yandYcopy the current cell, or the row's cells joined by tabs, into the registers (list.yank {what, register}).z >/z <widen or narrow the current column byColumnWidthStep(2) cells, never below 3;z =makes every column automatic (list.resize-column,list.reset-columns;List.SetColumnWidth,ColumnWidths,ResetColumnWidths). The widths and the current column are remembered per table id in the state directory'stables.json(Runtime.TableState,SetTableState). Storytables/column-resize.Vstarts a visual row selection drawn into the marks:j/kextend it (they clamp), a row's application verb runs for every selected row throughlist.apply,escorVends it and the marks go back (list.visual,List.Visual,StartVisual,EndVisual). Storytables/visual-apply.list.applytakesargsfor every row's request, and a verb with a confirm asks once for all the rows.RowSelect()keeps a table on row selection: trees' lists (h/l expand and collapse), the?help modal and the Settings page use it.- y, Y, V and the width keys are hidden from the footer;
?help lists them. - cli: the groups rail's
h/leftback rows take the new guardlist-first-column, so in a tablehmoves left and goes back to the rail from the first column. Storycatalog/groups-table-h.
grid
- Per-column widths:
z >/z </z =on the grid noun (grid.resize-column,grid.reset-columns;Grid.SetColumnWidth,ColumnWidth,ColumnWidths,ResetColumnWidths). A given width replacesMaxCellWidthfor its column and is remembered under the grid's id.
o-story
- Goldens read
C|(c|dimmed) for a highlighted row that holds a table's current cell; the expect fieldcellnames the current cell's text.
v0.11.0
The terminal leader can wait for the next key indefinitely.
terminal-leader-timeout(TerminalLeaderTimeoutSetting, duration): how longleader_pendingwaits for the next key before it sends the leader alone.0never expires, so the leader waits like tmux's prefix; a positive value is the leader's own timeout.EnsureTerminalLeaderdeclares it besideterminal-leaderunless the app declared it first (with its own default); the framework default isDefaultBindingTimeout, and until settings.json holds a value the leader followsBindingTimeout. Other key sequences keepBindingTimeout. Storyterminal/leader-timeout-zero-waits.
v0.10.0
An unfocused list keeps its selected row, faded; the dims are settings. A list can clear its filter when enter picks a row.
Unfocused lists and dims
- A page list without the focus keeps its selected row in reverse video and is drawn faded as a whole, so the row an unfocused list is on (a tree beside a live terminal) stays visible and the focused row is the one highlight at full strength. A list in a modal stays plain until focused; a tree editing a line is not faded. Replaces the old rule "an unfocused list draws every row plain" (UX rule R5.1). Stories
lists/unfocused-dimmed,lists/beside-terminal-keeps-row. - Two framework settings, declared by
EnsureConfigSettings:dim-unfocused(percent an unfocused list fades toward the background, default 20) andmodal-dim(the page behind a modal, default 35, the backdrop's look until now). Both 0 to 90 (MaxDim); 0 is off. Styles.Fade(percent)blends the scheme's text colour toward its surface (lipgloss.Blend1D, perceptual); in mono or NO_COLOR any fade is the faint attribute.Styles.WithDims(modal, unfocused)sets the newStyles.BackdropandStyles.Unfocused;Model.Styles()applies the settings.NewStylesuses the defaults.- o-story:
highlightedcounts full-strength highlights only; the newdimmedcounts highlights under faint; goldens mark a dimmed-only rowr|.
Lists
ClearFilterOnEnter()(anInstanceOptionforAddListandAddTree): enter in the list's filter line chooses the selected match (list.filter-choose, guardlist-chooses): the filter clears, the selection stays on that row and on screen, the mode is normal. With no match enter keeps the filter, as on every list; other lists are unchanged. Storylists/filter-enter-clears.
Fixes
- Custom components' explicit
Block.Cursornow reachesModel.View().Cursorwithout a focused input, using the normal block or visual underline shape. Blocks without a cursor remain hidden; input and terminal cursor shapes are unchanged.
v0.9.0
List motions
- Every list answers Vim's paging motions in normal mode:
g gandGgo to the first and last row, and with a count to that row (5G,5gg,list.goto {position, row});ctrl+d/ctrl+umove half a page (a count is rows) andctrl+f/ctrl+ba page (a count is pages) (list.scroll {delta, half, steps}). A page is the list's own window, else the page body's height; the window moves with the selection. They clamp at the list's edges, never spill, and are motions (off the footer, undermovementin?). A row noun that binds the same key keeps it. Storylists/page-motions. - A table with more columns than fit scrolls them (
list.scroll-columns {to, delta, steps},List.ColumnOffset):$brings the last column into view,^(and0without a count) goes back,z l/z hmove one column. The first column stays; the unscrolled view still drops columns byPriority. The keys answer only on a table that hides a column or is scrolled. Storytables/column-motions. - A key-sequence prefix whose only remaining row is its generated
<prefix> esccancel (every sequence on it guarded off) no longer waits for a second key.
v0.8.0
The Settings page is redesigned after tuios's: a list of fields, not a table.
A state verb answers what changed since the host's last poll instead of every row, and the catalog tells a tool's own verbs from the framework's. Every list filters with /, and an Update no longer visits list rows.
Settings page
- Sections are tabs across the top instead of a rail, so the rows get the whole width.
taband]show the next section,shift+taband[the previous, wrapping (settings.section {step}, or{name}); each section keeps its selected row. With one section there is no tab row. - One row per setting: its name on the left, its value on the right. There is no header and no Value/Default/Source columns. A
•after the name marks a value that is not the default.‹ value ›marks a valueh/lsteps. - The description under the rows says what the default is and where the value comes from: not in settings.json, set in settings.json, or settings.json holds an invalid value (with the reason) so the default applies.
- List settings: a list setting's row shows its item count (
(none)when empty), and each item is a row directly beneath it with the name column blank. On a list's rowenterornadds an item at the end. On an item rowenteroriedits it,nadds one after it,d ddeletes it andrresets the whole list. An empty item is refused. u(orctrl+z) takes back the last change made on the page and shows its row. A value that was invalid in settings.json comes back as it was.uis offered only when there is a change to take back.backspaceanddeletereset a setting, asrdoes.- The page reopens on the section and row it was left on.
State cache and deltas
- A state verb run through
cli.Shelltakes--since LIST=HASH,...and--since-cache ID(cli.StateFlags,FormatSince/ParseSince;--since=asks for every list in full, with hashes) and answers JSON Lines: a header{"state":1,"cache":ID}, then per listunchanged, adelta(the changed and added rows whole at their indexes, the removed ids) or the list infull, each with its content hash (RecordHash,ListHash). An unknown hash, another cache id or a pruned version answers full, never wrong. The catalog declaresstateDelta: 1. - The extension side caches each list's last versions (
Shell.StateCache):(id, record hash)files under$XDG_CACHE_HOME/<bin>/state/<scope>/lists/<list>/, written temp + rename and content-addressed,Keep(2) versions per list andScopes(8) scopes, the rest deleted.StateCache.Scope(args)adds to the scope (a TUIOS server);StateCache.Fingerprint(args)(StatFingerprint(paths...)) answers an unchanged poll without running the verb, except under--fresh;StateCache.Offturns it all off. MountPageuses it: withPageConfig.FetchFlagsa page'sReloadsends its list hashes and applies the answer (PageView.ApplyState): unchanged touches nothing and skipsOnRows, a delta goes throughList.Patch, full throughSetRows; a list whose hash does not match after applying is fetched again in full, once.PageView.Refreshforgets the hashes. An older extension (nostateDelta) keeps full answers throughFetch.List.Patch(ListPatch{Upsert, At, Remove})(andPatchItems) isSetItemsover the patched rows: kept rows keep their instances, selection and marks, and only removed rows close.SetItemskeeps a table's measured widths when every row draws the same cells.cli.DecodeStateandRowReader.Answer()read a delta answer. BenchmarksBenchmarkStatePollandBenchmarkStateApplycover an 8,000-row poll, unchanged and with one row changed.
Builtin verbs
- Every ochazuke tool's catalog lists the framework's verbs beside the tool's, and some are application verbs (the outline's 14, the chooser's 3), so hosts mounted them as the tool's (
ai-outline,qbt-outline).Operation.Builtinnow marks every operation the framework registers: everythingNewRuntimeandNewModelregister, and what a framework constructor registers later (NewPicker,NewPalette,NewAppearance,grid.Register,markdown.Register,cli.AddCommands). An application's verb is never builtin, even on a framework noun (history.capture-hook). - The catalog's verbs carry
builtin(cli.CatalogVerb.Builtin).CatalogDoc.AppNouns()is what a host mounting a tool's verbs mounts: each noun with only its non-builtin verbs (CatalogNoun.AppVerbs()), nouns left with none dropped. AMountPagelist of a noun's verbs lists only the tool's.
Every list filters
/on a focused list (page lists, tables, catalog lists, the Settings page's section, a list in a modal) opens a query line above the rows; typing keeps the rows whose label or cells hold every word, ignoring case, in list order,N of Mon the right. In thelist_filtermode up/down andctrl+n/ctrl+pmove among the matches (no spill), enter keeps the filter (/edits it,escclears it), esc clears it and selects the row/found, backspace on an empty query leaves,ctrl+uempties it.- Rows are hidden, never removed:
SetItemsandPatchfilter the new rows and keep the selection by id, marks survive,list.applyacts on the marked rows shown, focus landing on a hidden row clears the filter. - An application's own
/wins at any level: the framework's is anapprow (guardlist-filterable), its/ filterhint last on the footer.
Faster updates
Runtime.components()(everyDeliverandPendingCommands) walks an index of the instances that are not list rows instead of sorting every instance, and the list verbs no longer copy every row into their result.BenchmarkUpdateKeyat 16,000 rows: 818 µs to 11 µs a key; nabe's ai page 295 µs to 32 µs.
API changes
-
SettingList("list"): a setting whose value is[]string. settings.json keeps it as a JSON array,settings set NAME '["a","b"]'sets it, andSettings.Strings(name)reads a copy. A value of another shape is refused, as are anEnumand a default that is not a list of strings. -
settings.section {step, name}andsettings.undo. The nouns aresetting-item(edit,add,delete,reset) andsetting.add. The guards aresetting-listandsettings-undo. -
A table whose columns have no titles draws no header (
List.View). -
The page's lists are
__settings/list[/SECTION](were__settings/table[/SECTION]); the__settings/sectionsrail and thesettings-sectionnoun are gone. Item rows are__settings/item/NAME/INDEX. -
Operation.Builtin,Runtime.Builtin(register func())(marks the operationsregisteradded; for framework packages),cli.CatalogVerb.Builtin,CatalogDoc.AppNouns(),CatalogNoun.AppVerbs(). -
list filter --target LIST --query Q(empty clears),list.filter-open|type|erase|keep|clear,list.move {clamp};List.SetFilter,Filter,Filtering,Shown,Height(Lenplus the query line);ListFilterMode.Items/Lenstay every row; a filtered list's state hasfilterandtotaland itsitemsare the rows shown. -
cli.StateDeltaVersion,StateFlags,FormatSince,ParseSince,StateList,RecordHash,ListHash,StateCache,StatFingerprint,DecodeState,RowReader.Answer(),PageConfig.FetchFlags,PageView.ApplyState,PageView.Refresh,PageView.SinceFlags();ochazuke.ListPatch,List.Patch,PatchItems.
Migrating
- Stories or tests that read the old columns (
Setting,Value,Default,Source) or the rail (Sections,__settings/section…) read the tabs, the rows and the description instead. - A host that mounts another tool's catalog verbs (nabe, mtm) mounts
doc.AppNouns()instead of comparing keys against a freshNewRuntime()+NewModel(). A catalog from a tool built before this release has nobuiltin, so every verb reads as the tool's; keep the old check as a fallback until the tools are rebuilt.