aboutsummaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAgeFilesLines
* chore: symlink claude rules to rulesets canonicalHEADmainCraig Jennings2026-05-073-385/+3
| | | | | | The .claude/rules/ files (commits.md, testing.md, verification.md) were local copies that drifted from the canonical at ~/code/rulesets. I replaced them with absolute symlinks pointing at the canonical files. This repo now picks up rule updates automatically. The symlinks are absolute. Cloning this repo on a new machine needs ~/code/rulesets at the same path. Otherwise the symlinks dangle.
* docs: restore 'and on cjennings.net' in README install introCraig Jennings2026-04-301-1/+1
|
* docs: trim README — drop quick-sdcv comparison + bare-repo asideCraig Jennings2026-04-301-8/+1
|
* docs: expand README with usage examplesCraig Jennings2026-04-301-4/+317
| | | | | | | | | | | | | | | | | | | | Replaces the placeholder with full usage docs modelled on chime's README but tighter — gloss is a smaller package, the doc shouldn't out-bulk it. Sections: nav links, status, why-not-quick-sdcv, features, installation (package-vc-install / use-package :vc / straight / manual), quick start (lookup and add flows), the C-h g keybinding table plus the gloss-add-mode binding table, the four defcustoms with rationale, extending the source registry for v2+, org-drill integration, troubleshooting (libxml absent, network failures, file corruption, cache out of sync, side-window dismiss), development (Cask + ert-runner + Make targets), license. The keybinding table is the source of truth — every entry matches what `gloss-prefix-map' actually binds. The defcustoms section names the four actual customs only (gloss-file, gloss-fetch-sources, gloss-fetch-timeout, gloss-debug); no aspirational knobs.
* docs: record four ADRs for gloss design decisionsCraig Jennings2026-04-305-6/+215
| | | | | | | | | | | | | | | | | | | | The four decisions called out in the brainstorm now have their own files under docs/decisions/, each with Context / Decision / Consequences / Alternatives Considered. - 0001 — storage path default: respects org-directory if set, falls back to user-emacs-directory. - 0002 — auto-fetch on local miss: silent fall-through, network failures surface via the regular error rollup. No y/n prompt for v1. - 0003 — drill direction: every entry exports as twosided. One card per entry, both directions over time, no per-entry override. - 0004 — HTML strip strategy: libxml-parse-html-region. Plain text only, no italic/bold preservation. Online fetch disabled package-wide for the session if libxml is missing. The "Open Questions" section in the design doc is now "Decisions Recorded" with links into the ADRs.
* refactor: rework gloss-add UX to single side-window bufferCraig Jennings2026-04-303-49/+186
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The previous shape opened a regular (non-side) buffer for body input and showed the saved entry in the side window after C-c C-c. That left an extra window split during the typing phase and a side popup the user didn't ask for. New shape, modeled on `org-capture': - `gloss-add' renders the term and underline as a read-only header in *gloss-add: TERM*, leaves the body region beneath it editable, and pops the buffer in the side-window slot. Point lands at the body start so the user can type immediately. - `gloss-add-finish' reads the body via the `gloss-add--body-start' marker, saves with source `manual', kills the buffer, closes the side window, and echoes `gloss-add: saved TERM' for confirmation. - `gloss-add-abort' kills the buffer and closes the side window. - The shared `gloss--add-cleanup' helper handles kill + window-close for both finish and abort. Read-only header uses text properties (`read-only', `front-sticky', `rear-nonsticky') rather than narrowing, so the user can't escape the restriction with `C-x n w'. `gloss--add-finish-internal' no longer calls show-entry — the save is its only responsibility. The display decision (show or not) is the caller's, which lets `gloss-add-finish' choose "save and close" while `gloss-lookup' still chooses "save and show." The previous saved-window-config approach is dropped — the side-slot takeover means there's nothing to restore. Layout returns to its pre-add state on either C-c C-c or C-c C-k. Adds `tests/test-gloss--add-flow-smoke.el' covering the four interactive moments (open, finish, abort, empty-term guard) plus the read-only-header invariant. Updates the `gloss--add-finish-internal' tests to drop the show-entry assertion. 129 tests pass in 0.27s.
* feat: implement gloss secondary commandsCraig Jennings2026-04-302-15/+157
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Five interactive commands plus the supporting major mode and pure helpers for `gloss-add'. `gloss-add' opens a `*gloss-add: TERM*' buffer in `gloss-add-mode', a `text-mode' derivative with C-c C-c to save and C-c C-k to abort. The pure save side, `gloss--add-finish-internal', validates term and body, trims trailing whitespace, and delegates to `gloss-core-save' with source `manual' before showing the new entry. `gloss-edit' resolves a term to its source-buffer marker, jumps point there, unfolds the entry under both the legacy `org-show-entry' and the post-9.6 `org-fold-show-entry' (with-no-warnings on the fallback), and installs `gloss--after-save-refresh-cache' as a buffer-local after-save hook so manual edits keep the cache honest. `gloss-list-terms' prompts via `completing-read' over `gloss-core-list' and dispatches the chosen term to `gloss--lookup-flow'. Empty glossary raises a user-error. `gloss-stats' formats a multi-line report (total / by-source / drill-tagged / file size / cache mtime) via the pure helper `gloss--stats-text' and shows it in `*gloss-stats*' under `special-mode'. Drill counting walks the file via `org-map-entries' to be safe against tag-substring false positives. `gloss-reload' is a thin wrapper that resets the in-memory cache and re-ensures it from disk. `gloss-drill-export' is a thin wrapper around `gloss-drill-export-all'. Audit fold-in: renamed the stats-text missing-file test from `-reports-zero-and-never' to `-reports-zero' to match what the assertion actually checks (the file is auto-created on first call, so mtime is set, not "never"). Filed as a v1.1 follow-up: `gloss.el' reaches into `gloss-core--cache-reset' and `gloss-core--cache-ensure' double-dash-private functions. Decide whether to treat double-dash as "package-private" idiom or to expose public aliases. 125 tests pass in 0.25s — 111 prior plus 14 new across the six new files. No byte-compile warnings.
* test: add gloss secondary commands test suite (red phase)Craig Jennings2026-04-306-0/+230
| | | | | | | | | | | | | | | | | | | Six test files for the remaining stub commands. All 14 tests fail at this commit because the implementations are stubs. `gloss--add-finish-internal' (the pure save side of `gloss-add') gets N/B/E coverage on validation and the persistence side effect. `gloss--stats-text' (the pure stats string formatter) covers empty, populated, and missing-file cases. The interactive commands (`gloss-edit', `gloss-list-terms', `gloss-reload', `gloss-drill-export') get smoke tests only — the design treats them as mode-glue with 70% coverage targets, since prompts and `switch-to-buffer' are framework behaviour Emacs already tests. Two error-path tests assert the message contains a specific substring, not just that `user-error' was raised. The stubs raise `user-error' too, so a bare `should-error' would pass for the wrong reason. The substring check anchors red against the real error path.
* feat: implement gloss orchestration core (lookup + fetch-online)Craig Jennings2026-04-301-5/+56
| | | | | | | | | | | | | | | | | | | | | | | | | | | | Two pure-ish helpers and two interactive entry points. `gloss--orchestrate-fetch-result' is a pure pattern-matcher that classifies a fetch result into one of five decision symbols. The ordering matters: definition count gates first, then the error taxonomy. All-empty falls through to `:error-no-defs' so the user never sees a silent no-op. `gloss--lookup-flow' is the orchestration. Cache hit dispatches straight to display. Cache miss runs a fetch, classifies via the helper, and either auto-saves the lone definition, prompts the user to pick, or messages the right error. The `force-fetch' arg lets `gloss-fetch-online' reuse the same flow without duplicating logic. `gloss-core-save' is called with the `replace' collision action so force-fetch over an existing entry replaces it cleanly. On a real cache miss the entry is fresh, so `replace' is moot — the action only matters when the term is already there. `gloss-lookup' and `gloss-fetch-online' are now thin interactive wrappers around `gloss--lookup-flow'. The remaining stubs (add, edit, list-terms, stats, reload, drill-export) still raise user-error. 111 tests pass in 0.23s — 98 prior plus 13 new across the two new files.
* test: add gloss orchestration core test suite (red phase)Craig Jennings2026-04-302-0/+176
| | | | | | | | | | | | | | | | Two test files for the orchestration core. All 13 tests fail at this commit because the implementation is still stubbed. `gloss--orchestrate-fetch-result' gets full N/B/E coverage on the decision matrix: single def, multi def, the >1 boundary, empty defs with each combination of :no-defs and :failed populated, and the all-empty degenerate case. `gloss--lookup-flow' covers cache hit (no fetch), cache miss with one def (auto-save), cache miss with multiple (picker), cancelled picker (no save), no-defs error, and the force-fetch override that bypasses the cache. Mocks live at network and UI boundaries; persistence runs against a real temp glossary so the save side effect is validated.
* feat: implement gloss-drill org-drill exportCraig Jennings2026-04-302-13/+73
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Two public commands plus a small helper. `gloss-drill-export-all' walks `gloss-file' via `org-map-entries' and adds `:drill:' tag and `DRILL_CARD_TYPE: twosided' property to every top-level entry. Membership and equality guards make the operation idempotent: re-running adds nothing and writes nothing. `gloss-drill-untag-all' is the reverse, and intentionally does not require `org-drill' to be installed (the user might be cleaning up after uninstalling). The walking logic factors into a single private helper `gloss-drill--map-entries' that handles file open, modtime verification, org-mode activation, the level-1 filter, and a write-only-if-modified save. Both public commands compose it with their respective per-entry mutators. `org-drill' presence is checked with `featurep' before any walk so the file is never touched when the dep is missing. The user-error message includes the install command. Folds in a small fix to the idempotency test helper: the original used `throw' from inside `org-map-entries' but did not return the count to the caller. Switched to `catch' / `throw' with the count as the throw value. 98 tests pass in 0.24s — 88 prior plus 10 new across the four scenarios named in the design doc (tags-untagged, skips-already-tagged, no-orgdrill-installed, untag-all).
* test: add gloss-drill test suite (red phase)Craig Jennings2026-04-305-0/+260
| | | | | | | | | | | | Four test files plus a small testutil for toggling the `org-drill' feature flag. All 10 tests fail at this commit because the implementation is still a stub. The suite covers Normal (untagged entries get the tag and property), Boundary (empty file, idempotency, untag never-tagged), and Error (org-drill not installed). The error path also asserts the file is left untouched. Untag-all is tested under both feature states because the user might want to remove tags after uninstalling org-drill.
* chore: add Cask + ert-runner test infrastructureCraig Jennings2026-04-305-7/+42
| | | | | | | | | | | | | | | Replace the bare emacs --batch test runner with cask exec ert-runner. The Makefile test targets now delegate. validate-parens, compile, and lint stay on bare emacs --batch. Running cask once per file would slow them down for no real win. Adds: - Cask file declaring sources and ert-runner as a dev dep - .ert-runner config adding . and tests to load-path - tests/test-helper.el placeholder for future test setup - .gitignore entry for .cask/ All 88 tests pass in 0.19s via the new path.
* feat: implement gloss-display UI layerCraig Jennings2026-04-302-2/+74
| | | | | | | | | | | | | | | | | | | | | | | | Side-buffer display for gloss entries plus the picker shown when an online fetch returns multiple candidates. The major mode `gloss-mode' derives from `special-mode' so `q' quits the window for free. Two pure helpers handle the formatting: `gloss-display--format-candidate' renders a definition plist as a single-line "[source] text" row for `completing-read', truncating with an ellipsis when the row would exceed 80 chars. `gloss-display--render-entry' produces the term, an underline matching the term length, a blank line, then the body. The picker maps the user's choice back to the original plist via an alist, returning nil on `C-g' rather than letting the quit signal propagate. `gloss-display-show-entry' opens the buffer in a 40%-width side window on the right, idempotent on repeat calls for the same term. All 88 tests pass, including 23 new tests across format-candidate (full N/B/E), render-entry (N/B/E), pick-definition (mocking completing-read at the boundary), and a show-entry smoke test that mocks display-buffer to keep batch runs windowless.
* test: add gloss-display test suite (red phase)Craig Jennings2026-04-294-0/+280
| | | | | | | | Four test files covering the gloss-display public API and pure helpers. All 23 tests fail at this commit because the implementation is still a stub. Format-candidate gets full N/B/E coverage. Render-entry gets the pure-helper treatment. Pick-definition mocks completing-read at the boundary. Show-entry has a single smoke test.
* refactor: switch gloss-fetch result to uniform plist shapeCraig Jennings2026-04-289-63/+68
| | | | | | | | | | The previous shape (:ok DEFS) | (:empty :no-defs (...) :failed (...)) was malformed as a plist. The :empty tag at position 0 shifted the plist alignment. plist-get on :no-defs or :failed returned nil. Tests had to use (plist-get (cdr result) ...) as a workaround. The new shape is a uniform plist with all three keys always present: (:defs DEFS :no-defs (SYM ...) :failed (SYM ...)). Consumers branch on whether :defs is non-empty. There is no tag. plist-get works uniformly across success and empty cases. Updated gloss-fetch.el (rollup function and docstrings), 7 test files, and the design doc (docs/design/gloss.org § Error Handling). Tested by `make test`. 65 tests pass in 0.36 seconds.
* feat: implement gloss-fetch network layerCraig Jennings2026-04-287-20/+274
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Walks the `gloss-fetch--sources' registry in the order set by the `gloss-fetch-sources' defcustom and aggregates per-source results into the public `gloss-fetch-definitions' shape. The Wiktionary REST fetcher GETs the page-definition endpoint, parses JSON, walks only English (`en') entries, and HTML-strips each sense via `libxml-parse-html-region'. A sense whose strip fails is dropped while the source keeps its `:ok' status with N-1 entries. The HTTP-status taxonomy is five values: `:ok', `:no-defs' (404 or no English senses on a 200), `:rate-limited' (429), `:server-error' (5xx, malformed JSON, schema mismatch, 4xx other than 404 or 429), and `:unreachable' (nil from `url-retrieve-synchronously', or a signaled error). The `:reason' string carries technical detail to *gloss-debug* and never reaches the user. libxml is probed once per session at first fetch. When absent, online fetch is disabled package-wide and every call signals `user-error' with the install hint. `url-retrieve-synchronously' is wrapped with the `gloss-fetch-timeout' defcustom (default 5 seconds). Tested with `make test'. 60 of 62 tests pass. The two pending failures load Wiktionary fixtures via `gloss-test--load-wiktionary-fixture', which is provided on a parallel branch and will pass once both branches land. The implementation has been verified against the captured fixtures end-to-end (anaphora returns 4 senses, SBIR returns 2, matching the design's expected counts).
* test: add gloss-fetch test suite (red phase)Craig Jennings2026-04-289-0/+509
| | | | | | | | | | | | | | | | Eight test files cover the network layer's public and internal contract. The boundary mock is `url-retrieve-synchronously', wrapped by a small `testutil-gloss-fetch' helper that builds response buffers in the shape the url library returns. Tests cover the 200 happy paths (anaphora and SBIR fixtures), 404 to :no-defs, 5xx and 4xx-other and malformed JSON to :server-error, 429 to :rate-limited, nil-from-url to :unreachable, the libxml availability probe (one-shot, signals user-error when absent), the registry walker ordering, and the pure HTML strip helper across N/B/E. Tests fail on missing `gloss-fetch--*' functions, as expected for red phase.
* test: add Wiktionary fixture loader helperCraig Jennings2026-04-282-0/+54
| | | | | | | | Append `gloss-test--load-wiktionary-fixture' to tests/testutil-gloss.el. It takes a fixture name (e.g. "anaphora") and returns the raw JSON body from tests/fixtures/wiktionary-NAME.json, or signals `error' with the full path when the file isn't there. The helper resolves the fixtures directory from a `defconst' captured at load time. That way it works the same whether a test file requires testutil-gloss directly or pulls it in transitively through `make test'. Three ERT cases under tests/test-testutil-gloss--load-wiktionary-fixture.el cover Normal (anaphora loads as a non-empty JSON string), Boundary (the smallest fixture, 404, loads), and Error (a missing fixture raises with the path embedded in the message). Verified with `make test': 35 passed, 0 unexpected.
* chore: capture Wiktionary REST fixtures for replayCraig Jennings2026-04-285-0/+5
| | | | | | Save raw response bodies from the Wiktionary REST endpoint under tests/fixtures/. The fetch layer can replay them with a cl-letf on url-retrieve-synchronously instead of hitting the network in tests. The five fixtures cover the cases that matter for the parser. anaphora is the simple single-sense English entry. SBIR is an acronym with multiple senses. API is highly polysemous and multi-language (en, fr, id, la, pt). hapax-legomenon is the multi-word case, so it exercises URL-encoding for the space. The 404 fixture captures the JSON error body Wiktionary returns when a term isn't there.
* refactor: extract missing-glossary test helperCraig Jennings2026-04-285-43/+34
| | | | | | | | Four tests across lookup, list, find-buffer-position, and first-call-creates-file shared the same boilerplate. Each let-bound gloss-file to a randomized nonexistent path, wrapped in unwind-protect, reset the cache, and cleaned up file and buffer afterward. Extracted as gloss-test--with-missing-glossary in testutil-gloss.el, parallel to the existing gloss-test--with-temp-glossary. The four call sites drop from 8-10 lines each to 2-3. Tested by running the full 32-test suite. All 32 pass in 0.21 seconds.
* feat: implement gloss-core data layerCraig Jennings2026-04-282-18/+271
| | | | | | | | | | Public API: gloss-core-lookup, gloss-core-save, gloss-core-list, gloss-core-find-buffer-position. Save inserts entries at the alphabetical position (case-insensitive compare), creates the file and parent directory on first call, prompts on collision via completing-read over Replace/Append/Cancel, and updates the in-memory cache directly. Lookup checks gloss-file's mtime against the cached load time. If disk is newer than the buffer, it reverts the buffer first. Out-of-band edits land on the next read. Parser failures during reload preserve the existing cache and surface a one-line message. Tested by the 32-test suite from the previous commit. All 32 pass in 0.16 seconds. The defgroup and defcustoms (gloss-file, gloss-debug) live here rather than in gloss.el. That keeps the data layer self-contained when tests load it directly without the orchestration layer.
* test: add gloss-core test suite (red phase)Craig Jennings2026-04-289-0/+481
| | | | | | | | The commit lands eight per-function test files and a shared testutil. 32 tests across Normal/Boundary/Error categories cover the public API (lookup, save, list, find-buffer-position) and the internals that need observable behavior tests (mtime invalidation, corrupt-file resilience, alphabetical insert, first-call file creation). All 32 fail with void-function on the gloss-core symbols. That is the intended red-phase signal. The next commit lands the implementation that turns them green. testutil-gloss provides a with-temp-glossary macro. It binds gloss-file to a temp file, resets the cache before and after, and cleans up the visiting buffer.
* chore: scaffold gloss packageCraig Jennings2026-04-2818-0/+2042
Five layered files per the design at docs/design/gloss.org. gloss-core for the data layer, gloss-fetch for the network layer, gloss-display for the UI, gloss-drill for the spaced-repetition export, and gloss.el as the entry point. All five are skeletons. Implementation comes next. The Makefile delegates to ert with the usual unit, integration, and per-file targets. It also runs paren and lint passes. The package is licensed GPL-3.0-or-later. README is a placeholder pointing at the design doc.