Skip to content

feat: the bar's tool tables are files, not Go - #1

Merged
butschster merged 4 commits into
mainfrom
feat/tool-files-for-the-bar
Aug 8, 2026
Merged

butschster merged 4 commits into
mainfrom
feat/tool-files-for-the-bar

Conversation

@butschster

Copy link
Copy Markdown
Contributor

What

The per-tool button tables move out of server/actions.go and become one YAML
file per program: shipped in server/tools/ and compiled in with go:embed,
overridable per machine from ~/.config/linux-terminal/tools/.

Two smaller things ride along: the app now shows its own version beside its name
and each server's version on its card, and the terminal's bar button wears the
keyboard mark instead of the word.

Why

Teaching the bar a program was an edit, a rebuild and a restart — and the restart
is the step that gets forgotten, so the change looks like it did not work. It also
took two edits in different files, the key table and the classifier, which
could disagree.

Now it is one file, and editing it changes the bar in an open session within about
three seconds.

How

  • match: lives in the file, so a file both classifies the program and describes
    its keys. Only names without a table stay in Go (git, ssh, psql,
    aider), and they fall back to pager keys.
  • The file's name is the tool's identity — no id field inside to disagree with it.
  • Hot reload is one stat at most once a second; a generation counter makes each
    session re-push its bar, otherwise an edit would not arrive until the directory
    or the running program changed.
  • when: {installed: …} is the only condition, and it covers every case the old
    Go tables had. Deliberately not a scripting language: a script would run four
    times a second inside the process that holds the pty and the pairing token, and
    what it would buy is a table of buttons.
  • Groups computed from the machine — places, projects, skills, run — stay in Go.
    A directory listing is not a table.
  • --tools prints what loaded, from where, and which program names select it.

Testing

go test ./... — 13 tests, and they earned their place immediately: the
duplicate-key test failed on the first run and found two shipped buttons that
send the same bytes as keys the client already draws always (^W in nano, Tab
in codex). Both removed.

Also covered: escapes decode to real control bytes (single-quoting them in YAML
silently sends the literal characters), no two files claim the same program, a
broken key costs its own line rather than the file, user files override and their
removal restores the built-in, and the reload moves the generation.

Verified by hand on an emulator, end to end: vi classified through the file's
match: list, the bar built from the file, a user override changed it live
without a restart or a reconnection, and deleting the override brought the
built-in back.

New skills: new-tool (the format, escapes, hot reload, why a file is being
ignored) and emulator (running the whole flow without a headset).

A YAML file per program, embedded and overridable from
~/.config/linux-terminal/tools, re-read within a second and pushed to
every open session — no restart, no reconnection, no new APK. The match
list lives in the same file, so adding a tool is one file rather than two
edits that can disagree. The groups computed from the machine stay in Go.

Also: the app shows its own version and the server's, the terminal's bar
button wears the keyboard mark instead of the word, and the README leads
with what this is for.
@butschster butschster added the enhancement New feature or request label Aug 7, 2026
Both halves, because a server that compiles and an app that does not is a
release that fails at the tag. The Go version comes from go.mod in both
workflows now — release.yml pinned 1.24 while the module asked for newer,
and only the toolchain download hid it.
The parts that decide whether a stranger on the network gets a shell, written
against the wire: a real handshake, real frames. The four properties that make
six digits enough are one test each, since removing any one still looks like it
works.

Found and fixed: an identity with no token accepted an empty token, because a
constant-time compare of two empty strings is equal. Unreachable today —
ensureIdentity always fills it — and now unreachable by a second route too.
Both sides declared a constant on the same line of ContextBar. main added ESC,
which the paging rocker needs because PgUp and PgDn are fixed controls and a
fixed control cannot wait for the host to send its bytes. This branch had
replaced the local keyboard mark with the one that now lives in Buttons, so the
terminal window and the bar draw the same glyph from one definition.

Both are kept. They answer different questions, and the merge changes no value:
the keyboard mark is the same codepoint it was before it moved.

Verified rather than assumed: the server builds, vets and passes its tests, and
the client assembles.
@butschster
butschster merged commit cbffc17 into main Aug 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant