// ZSH-LEARN — ENGINEERING REPORT

MySQL / MariaDB-backed knowledge plugin · le to save · se / see / seee to search · qu / qua to quiz · Ctrl-K ZLE widget

>_EXECUTIVE SUMMARY

zsh-learn is a MySQL/MariaDB-backed flashcard plugin for zsh. Every entry is a row in a $ZPWR_SCHEMA_NAME.$ZPWR_TABLE_NAME table (default root.LearningCollection); the plugin ships save / search / random-recall / quiz / SQL-passthrough / in-place-edit / delete operations as autoloaded zsh functions. le "git rebase -i HEAD~3" writes the row, se rebase searches it, qu 50 pipes 50 random entries into fzf for spaced-recall drilling. The plugin's database surface is the same one consumed by the LearningCollectionAPI Spring Boot service — the zsh CLI and the web/REST API operate on the same authoritative table. Source: 833 zsh lines across 1 plugin entry + 26 autoload functions + 1 completion. Pinned by 457 @test blocks across 7 zunit files.

833
Zsh Lines
457
@test Blocks (zunit)
7
Test Files
26
Autoload Fns
16
Aliases (typo-proof verbs)
3
ZLE Bindkeys (viins/vicmd/emacs)
1
Compdef (_se)
6
fzf Completer Hooks

~ARCHITECTURE

Single entry point that conditionally registers ZLE + zstyle + bindkeys, then autoloads every function under autoload/ via autoload -Uz "${0:h}/autoload/"*(.:t). Each verb is its own one-file function — lazy-loaded on first use.

File / GroupLinesRole
zsh-learn.plugin.zsh 137 Plugin entry. Declares ZPWR_VARS association; sets defaults for ZPWR_SCHEMA_NAME / ZPWR_TABLE_NAME / ZPWR_LEARN_COMMAND / temp paths; declares 16 alias-as-verb pinned shortcuts (le, se, see, seee, re, rsql, editl, delid, plus typo variants es/ees/ses/ese/sse/ssee/rer/er); populates ZPWR_VERBS association when zpwr is loaded; registers per-completion zstyle (group-order, list-colors, sort-disabled for numeric IDs); binds Ctrl-K across viins / vicmd / emacs keymaps; prepends ${0:h}/src + ${0:h}/autoload to fpath; autoloads all 26 functions under autoload/.
autoload/zsh-learn-Learn 40 Core ZLE widget. Prompts via the current line buffer, formats the entry, dispatches to zsh-learn-Savel. Bound to Ctrl-K.
autoload/zsh-learn-Savel 57 The insert path. Escapes apostrophes, length-checks against ZPWR_LEARN_MAX_SIZE (default 3000), composes the SQL INSERT, dispatches via $ZPWR_LEARN_COMMAND $ZPWR_SCHEMA_NAME (default mysql root).
autoload/zsh-learn-Searchl · Searchle · Searchlee 71 / 72 / 21 Three-tier search functions, fronted by se / see / seee respectively. Each tier widens the projected column set (learning → learning+category → learning+category+date). All three accept filter arguments and apply them via SQL WHERE ... LIKE ....
autoload/zsh-learn-Redo · Redosql 21 / 29 SQL passthrough. re ID prints the matched row plus a ready-to-edit UPDATE statement; rsql ID opens the same in $EDITOR.
autoload/zsh-learn-Editl · DeleteId · del 35 / 11 / 9 In-place edit via $EDITOR; targeted delete by ID; bulk delete of last N rows.
autoload/qu · qua · ser · sera · sef 14 / 8 / 13 / 8 / 12 Random-recall family. ser N = N random rows; sera = all randomized; qu N = same as ser but piped to fzf for interactive quiz; qua = all randomized into fzf; sef = recency-ordered fzf browse.
autoload/zsh-learn-CreateLearningCollection · DropLearningCollection 65 / 61 DDL admin. Create emits the schema (id PK + auto-increment, learning TEXT, category VARCHAR, timestamp), Drop tears it down. Largest single autoload files because they carry the literal DDL strings.
autoload/zsh-learn-Get · GetItems · GetLastItem 20 / 42 / 12 Read primitives used by the search/redo verbs internally.
autoload/_fzf_complete_{se,redo,rsql} + _post 11 + 8 (× 3) fzf-tab integration hooks. When fzf-tab or fzf's shell-completer is loaded, these widgets feed candidate IDs / learning rows directly into fzf.
src/_se 18 compdef completion shared by se / see / seee / re / rsql / zsh-learn-Redo / zsh-learn-Redosql / zsh-learn-Searchl / zsh-learn-Searchle / zsh-learn-Searchlee.
Total source 833 1 entry + 26 autoloads + 1 completion · pure zsh

$VERB SQL CONTRACT

Every verb is a thin shell wrapper that composes a SQL statement and pipes it into ${=ZPWR_LEARN_COMMAND}. The table below is the exact statement each verb emits (table name templated as $ZPWR_SCHEMA_NAME.$ZPWR_TABLE_NAME, default root.LearningCollection). No ORM, no abstraction layer — the SQL is the contract.

VerbArgsSQL / behavior
le (Savel)[-e] [-c CAT] LEARNINGinsert into … (category, learning, dateAdded) values (CAT, LEARNING, now()). -c sets category (default programming); -e opens $EDITOR on a temp file instead of taking an inline arg; apostrophes escaped via sed; rejected if $#learning > ZPWR_LEARN_MAX_SIZE (default 3000 chars).
se (Searchl)[FILTER...]select learning from … order by dateAdded, numbered with nl, then each FILTER applied via ag -i (if installed) or grep -i. No filter = full dump. Colorized when ZPWR_COLORS=true.
see / seee[FILTER]see projects learning + category; seee (Searchlee) does select id, dateAdded, learning, category from … order by dateAdded numbered with cat -n, filtered via perl + ag/grep.
ser / seraser [N]ser = zsh-learn-Searchl | shuf -n N (default 100); sera = zsh-learn-Searchl | shuf (all rows shuffled).
qu / quaqu [N]qu = ser N | fzf -m --ansi (default 100); qua = sera | fzf -m --ansi. Interactive multi-select quiz.
sefzsh-learn-Searchl | tac | fzf --ansi -m, then the selected IDs are pushed back onto the command line as a zsh-learn-Redo ID… call via source /dev/stdin — recency-first browse that lands you in the redo/edit path.
re (Redo)[ID|REGEX]...No arg = last row's update … set learning=… where id=… pushed to the buffer via print -rz. With args, each numeric ID or regex resolves through zsh-learn-GetItems to a ready-to-run update line.
rsql (Redosql)[ID|REGEX]...Same selection as re, but writes the update statements to $ZPWR_TEMPFILE_SQL, opens them in vim (or nvim when ZPWR_USE_NEOVIM=true), then pipes the edited file back through ${=ZPWR_LEARN_COMMAND}.
editl (Editl)IDSELECT learning … WHERE id = ID into a temp file, opens $EDITOR, then UPDATE … SET learning=… WHERE id = ID. Same length-cap + escaping as le.
del[N]delete from … order by id desc limit N (default 1). Bulk delete of the most-recent N rows.
delid (DeleteId)IDDELETE FROM … WHERE id = ID. Targeted single-row delete.
…-CreateLearningCollection[MYSQL_PW]Checks information_schema.tables; if absent, create schema if not exists then CREATE TABLE (category varchar(20), learning varchar($ZPWR_LEARN_MAX_SIZE), dateAdded datetime, id int AUTO_INCREMENT PRIMARY KEY). Optional first arg is the mysql password (else uses ~/.my.cnf).
…-DropLearningCollectionTears down the table.

=ENVIRONMENT

Every knob is a ZPWR_* environment variable, defaulted in the plugin entry with test -z … && export so a value set before sourcing wins. The table is the live default set from zsh-learn.plugin.zsh.

VariableDefaultEffect
ZPWR_LEARN_COMMANDmysqlThe database command every verb pipes SQL into. Set to 'sudo mysql' for MariaDB unix-socket auth.
ZPWR_SCHEMA_NAMErootSchema half of the templated table name.
ZPWR_TABLE_NAMELearningCollectionTable half. Pre-rename installs used the LearningCollectiion typo.
ZPWR_LEARN_MAX_SIZE3000Character cap enforced on le / editl insert; also the learning column width in the generated DDL.
ZPWR_VARS[maxRecords]2000Completion candidate cap — _se feeds at most this many rows into _describe.
ZPWR_LEARN(unset)Set to false to skip all ZLE / bindkey / zstyle / fpath / autoload registration — the opt-out gate.
ZPWR_COLORS(unset)true enables the perl-driven ANSI colorization in se.
ZPWR_USE_NEOVIM(unset)true makes rsql open nvim instead of vim.
ZPWR_TEMPFILE_SQL/tmp/.zpwr-sql-tempScratch file rsql writes its update statements to before editing.
ZPWR_LOGFILE/tmp/.zpwr-logstderr from the database command is appended here, keeping the search output clean.

#TEST COVERAGE

457 @test blocks across 7 zunit files. t-unit.zsh carries the bulk (432) — behavioral pins for every verb against a fixture $ZPWR_LEARN_COMMAND stub. The contract files pin install-path / fpath / autoload shape; t-savel-escaping.zsh pins the SQL-quote escaping and character-vs-byte length semantics of the insert path. The CI suite runs them under zunit on Ubuntu against a stubbed database command — no live mysqld required.

FileTestsPins
t-unit.zsh432Every verb (le, se, see, seee, ser, sera, qu, qua, sef, re, rsql, editl, del, delid, Create/Drop) emits the documented SQL shape against a mocked $ZPWR_LEARN_COMMAND; apostrophe-escape behavior; length-cap behavior; verb-typo aliases resolve to the canonical function.
t-savel-escaping.zsh4Pins two zsh-learn-Savel invariants the unit suite left unasserted: the '\' escape (sed at zsh-learn-Savel:42) actually fires before the apostrophe reaches the INSERT ... VALUES clause (SQL break-out guard), and the size guard at :49 counts characters ($#learning), not bytes — a 3-char multibyte string is not rejected by a 3-char limit.
t-syntax.zsh2zsh -n over plugin entry + autoload tree.
t-contract.zsh3Entrypoint stem matches dir; entrypoint parses; #compdef on every _* file.
t-contract2.zsh6fpath augmentation includes both src/ AND autoload/; autoload glob picks up every file under autoload/ with (.:t).
t-contract3.zsh5Zinit-style install path stability; ZPWR_LEARN=false opt-out skips bindkey/zstyle registration.
t-contract4.zsh5OMZ-style install path; ZPWR_VARS initialized as association on first source; ZLE widget registered exactly once.
Total4577 zunit files · verb-surface + install-contract + escaping

/INTEGRATION

Database backend

Default: mysql command, schema root, table LearningCollection. Override via ZPWR_LEARN_COMMAND (e.g. 'sudo mysql' for MariaDB unix-auth) and ZPWR_SCHEMA_NAME / ZPWR_TABLE_NAME. zsh-learn-CreateLearningCollection emits the DDL the first time.

LearningCollectionAPI cross-stack

The same $ZPWR_SCHEMA_NAME.$ZPWR_TABLE_NAME rows are consumed by the LearningCollectionAPI Spring Boot REST service — entries saved from zsh are immediately readable from the web app, and vice versa. One source of truth, two front-ends.

ZLE widget binding

zle -N zsh-learn-Learn registers the widget; the file then binds Ctrl-K in three keymaps (viins, vicmd, emacs) so the binding survives any $KEYMAP setting in the user's .zshrc. Vim-emulation users press Ctrl-K in insert OR command mode; emacs-mode users press Ctrl-K directly.

Completion: numeric IDs, ordered, color-coded

The plugin sets three zstyle contracts on the _se completion: sort false (preserve numeric-DESC server order rather than lexicographic), group-order to put IDs before learning text, and per-group list-colors for ID vs text contrast.

fzf integration

If fzf's shell-completer is loaded, the six _fzf_complete_se/redo/rsql hooks intercept the **TAB trigger and feed live database rows into fzf — same backend as the inline search, different presentation.

Lazy autoload + Zsh Plugin Standard

Each verb file under autoload/ is one function. The plugin entry registers them via autoload -Uz — nothing is parsed until first call. Honors the Zsh Plugin Standard 0= header so ${0:h} resolves correctly under zinit / oh-my-zsh / antibody / antigen / bare source.


!DESIGN DECISIONS

SQL backend, not an embedded SQLite

Embedded SQLite would isolate the data to one shell host. MySQL/MariaDB lets the same table be served by the LearningCollectionAPI Spring Boot service — the user's notes are accessible from any browser, any cron job, any sibling process. Cost: a running mysqld. Gain: a personal knowledge graph that isn't bound to one terminal.

Typo-proof verb aliases

16 aliases mapped to 7 canonical functions. es / ees / ses / ese / sse / ssee all dispatch to zsh-learn-Searchl; re / rer / er all dispatch to zsh-learn-Redo. Muscle-memory typos still work — the operator never has to remember the exact spelling.

3-tier search by projected columns

Naive plugins would have one search with a --verbose flag. zsh-learn instead ships three named verbs (se < see < seee) corresponding to projected-column count. Saves a flag, makes the cardinality explicit in the verb itself, and pre-renders the zstyle group-order per verb.

Three keymaps for one widget

bindkey applies to one keymap at a time. The plugin doesn't try to detect the user's mode — it just registers Ctrl-K against all three of viins, vicmd, emacs. Total cost: three bindkey calls. Total gain: never lose access to Learn after a mode switch.

noglob on every alias

Every alias is noglob zsh-learn-*. Search filters contain *, ?, [] — without noglob the shell would glob-expand them against the local filesystem before the function ever saw them. noglob at alias time is one keystroke for the operator and zero behavioral surprises.

Verb-as-file, not verb-as-block

26 separate files under autoload/ instead of one mega-file. Each function lazy-loads on first call, so cold-startup cost is just the entry-point parse (~137 lines). Editing one verb does not invalidate any compiled .zwc for the others.


@FOOTPRINT