Skip to content

feat(navigator-ui): the Vocabulary tab (M19b) - #32

Open
jgrodziski wants to merge 11 commits into
feat/m19b-vocabulary-cloudfrom
feat/m19b-vocabulary-tab
Open

jgrodziski wants to merge 11 commits into
feat/m19b-vocabulary-cloudfrom
feat/m19b-vocabulary-tab

Conversation

@jgrodziski

Copy link
Copy Markdown
Contributor

Second step of milestone M19b in PLAN.md §20 (word cloud / glossary, #6). Stacked on #31 (itself on #30, #29, #28, #27 and #26): review those first; this PR's diff is the eight commits after #31.

It adds the navigator's Vocabulary tab and the two server routes behind it: /vocabulary.json, the artifact, and /carriers.json, a lookup of the names that carry a word.

The carriers (carriers.ts)

  • One counting rule. The rule that turns an entity's name into weighted words moved out of aggregate.ts into reading.ts, so the artifact and the carriers read a name the same way. The artifact's snapshots are unchanged.
  • A row is the sum of its carriers. For a scope and a word, the carriers' weights, occurrences and count add up to the word's row in the artifact. A property checks it for every word of every scope in the six fixtures.
  • Same options or no answer. An artifact counted under other weights, another technical list or another view is refused, and so is one built from another model.
  • Who a carrier is. Types and modules carry their id. A method, field or parameter carries an owner instead (its type, else its module), as in the navigator, which has no node for it.
  • The query. No scope means the corpus. limit defaults to 200 and total always counts them all. A word that two languages classify differently is ambiguous, and the answer lists the classes to pick from with class.

On the Java fixture, order at the corpus has 12 carriers of total weight 40, which matches the hand count in the fixture README.

The routes (codegraph serve)

  • /vocabulary.json is built from the same model, view and name as the navigator and the city, so a scope's id is the navigator node's id.
  • /carriers.json?term=…[&class=…][&scope=…][&limit=…] answers 200 when found, 400 for a bad request or an ambiguous word, and 404 for an unknown scope or word. Bad parameters are refused before anything is built.
  • The index is built on the first answerable request, reading every kept entity once, and the graph is released then. A page nobody clicks costs nothing.
  • The desktop app's daemon builds vocabulary.json beside the navigator and the city, and rebuilds the page when one is missing. It has no /carriers.json, because it keeps no model between jobs (it serves no explanations either). The tab shows the counts there and says why the names are missing.

The tab (navigator-ui)

  • The cloud and a table of the scope selected in Navigate. A method or field shows its type's words. A breadcrumb steps up to the module and the corpus.
  • Class toggles, a top-N slider (5 to 200, default 40), and the same legend as the SVG.
  • Clicking a word lists its carriers, heaviest first, each with its role, weight and file. Clicking a carrier opens it in Navigate.
  • The layout is the same code as --svg. It comes through a new @codegraph/vocabulary/cloud entry, cloud.ts alone, which imports nothing. The guard test allows that one specifier and keeps the package's index types-only for the page; the page's constants are restated and pinned to the package's. The built bundle contains none of the Node pipeline.
  • No colour. Words are bright, dimmed or faint by class, because the page's four colours already mean incoming, outgoing, inferred and selected. The page measures with measureText in its monospace face.
  • The tab mounts on first visit and then stays alive while hidden, like the City tab.

Worth a look in review:

  • The daemon has no carriers route (above). The alternative was keeping each job's model in memory.
  • Carriers are capped at 200 per answer. Broadleaf's order has 3,303; the tab says "the 200 heaviest of 3,303".
  • The navigator tutorial on the website lists four tabs and already lacked City. Updating it means re-running its gson model for real numbers, so it moves to the next PR's website section.

Verification

  • pnpm -r build and pnpm -r typecheck green; eslint clean on every changed file; pnpm run gen:schemas leaves schemas/ unchanged; the Hugo site builds.
  • vocabulary 324 tests, cli 987, navigator-ui 105; every other package passes except the Node 26 node:sqlite warning test noted on feat(core): language lexicons on profiles (M19a) #26.
  • 17 deliberate mutations, one at a time, of the carriers, the browser entry's type-only check, the CLI wiring and the page's model; the tests caught each.
  • In the browser (headless Chrome at 1600×1000, on BroadleafCommerce):
    • The corpus cloud reads order item customer offer product…, and ProductImpl reads product category sale sku parent.
    • catalog.domain with product picked lists 239 carriers, and clicking a parameter among them opened Category in Navigate.
    • A page served with no carriers route shows the counts and the explanation.
    • No console error.
  • The single-executable (./build.sh --ts --sea) serves both new routes, and its smoke checks pass.

PLAN §20.2 records the carriers and the tab as built; §20.4's serve and tab items are checked.

Part of #6.

🤖 Generated with Claude Code

Jérémie Grodziski and others added 8 commits October 26, 2026 12:50
The artifact folds what a name says into every scope the entity belongs
to; the carriers lookup will keep it per entity. Both now read through
reading.ts (the split, the class, the role's weight, the qualifier), so a
carrier's share is exactly what the row counted. Snapshots unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vocabularyCarriers(graph, artifact, options) reads every kept entity once
and answers which names of a stored scope carry a term, each with what it
added to the row. A row is the sum of its carriers: a property over every
term of every scope of the six fixtures. A type or module carries its id,
a member the owner a page reveals it in; an artifact counted under other
weights, list or view is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codegraph/vocabulary/cloud is cloud.ts alone, built as its own entry: a
page can run the layout as a value without the analyzer, core, zod and the
SQLite loader behind the package's index. A test keeps cloud.ts's imports
type-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
codegraph serve builds vocabulary.json from the same graph, view and
name as the navigator and the city, and serves it at /vocabulary.json.
/carriers.json?term=…[&scope=…][&class=…][&limit=…] answers which names
of a stored scope carry a word, from the model the server holds: 404 for
an unknown scope or term, 400 for a request it cannot ask or a text the
scope speaks as two terms. The index is built at the first answerable
request, and the graph is let go then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The daemon's job writes vocabulary.json beside navigator.json and
city.json and serves it under the token. A project directory without one,
built before it existed, is rebuilt rather than reused. The daemon holds
no model between jobs, so /carriers.json is a 404 there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sixth tab: the words of the selected scope's names, the corpus until
a module or a type is selected (a member shows its type), as a cloud and
a table. The cloud is @codegraph/vocabulary/cloud's layout measured with
the page's monospace face; size is weight, colour the class alone, and a
legend states what was counted. Class chips, a top-N slider, the scope
chain to step up. A word asks carriers.json for its names, and each
reveals its node in Navigate; with no such route the tab says so.

The artifact kind, the carriers kind and the class list are literals
pinned to the package's; the package is imported for types only, its
./cloud entry as the one value. The Vite dev server serves
VOCABULARY_JSON as it does NAVIGATOR_JSON.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md's package map, docs/cli.md's serve section and the website's
serve, vocabulary, vocabulary.json and environment pages: the sixth tab,
the third artifact, the carriers answer and its statuses, the daemon
without a carriers route, VOCABULARY_JSON for the dev server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
codegraph 67ae45d Commit Preview URL

Branch Preview URL
Oct 27 2026, 03:05 PM

From feat/m19b-vocabulary-cloud.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jgrodziski pushed a commit that referenced this pull request Oct 27, 2026
The closure property timed out on CI (5.07 s on #32, 5.2 s on #28,
1.7 to 3.8 s on the runs between). The cost was the check, not the
build: over 100 sampled corpora, building took 17 ms and expectClosed
279 ms, an `expect` per row of every scope.

The same checks now run in plain code, collect every breach, and assert
once that there is none; a failure lists them all. The property drops
from about 320 ms to 34 ms locally. Doctoring a real artifact 34 ways,
one breach each, both the old and the new check fail every time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jgrodziski
jgrodziski added this pull request to stack #34 October 27, 2026 14:28
From feat/m19b-vocabulary-cloud.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From feat/m19b-vocabulary-cloud.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant