Liking cljdoc? Tell your friends :D

vaelii.browser.access

How a read client reaches a KB — directly in-process, or over the daemon HTTP API (vaelii.host.serve). It re-exports the slice of the vaelii.core read surface the browser uses, so the browser is written once against these names and runs unchanged against a local KB or a remote daemon.

A target is either a real KB (treated as local) or an access value from local / remote. A KB-read op dispatches on it:

:remote → the client (vaelii.host.client/call) — one HTTP round-trip :local → vaelii.core, via serve/ops (the very allowlist the daemon serves, so local and remote answer through the same table and cannot drift) a raw KB → the same local path (so a caller holding a plain KB needs no wrapper)

The pure display fns (term-role, reified-term?, readable-sentence, indexable-terms, negative?, rests-on, query-contexts, assertable-strengths, sort-by-content, levels, calculi), the bootstrap fns (open-kb, clear!), the in-process write-hazards and the process-wide switch-value take no target and delegate to vaelii.core. They are here so a caller can require this one namespace and reach the whole surface it needs.

Reads — including check / check-edit, which answer what assert would refuse and write nothing — plus the four writes the browser performs: edit (an assert/retract batch in one settle), edit-with-consequences (the same batch, plus the belief it moved), forward-chain, and preview (which stores nothing but applies a batch and rolls it back, so it holds the single writer). A remote result is already EDN-clean (the daemon projects sentex records to maps); a local result is the raw record, and both answer to the same keys, so a caller handles them identically.

How a *read* client reaches a KB — directly in-process, or over the daemon HTTP API
(`vaelii.host.serve`).  It re-exports the slice of the `vaelii.core` read surface the
browser uses, so the browser is written once against these names and runs unchanged
against a local KB or a remote daemon.

A **target** is either a real KB (treated as local) or an access value from `local` /
`remote`.  A KB-read op dispatches on it:

  :remote  → the client (`vaelii.host.client/call`) — one HTTP round-trip
  :local   → `vaelii.core`, via `serve/ops` (the very allowlist the daemon serves, so
             local and remote answer through the same table and cannot drift)
  a raw KB → the same local path (so a caller holding a plain KB needs no wrapper)

The pure display fns (`term-role`, `reified-term?`, `readable-sentence`,
`indexable-terms`, `negative?`, `rests-on`, `query-contexts`, `assertable-strengths`,
`sort-by-content`, `levels`, `calculi`), the bootstrap fns (`open-kb`, `clear!`), the
in-process `write-hazards` and the process-wide `switch-value` take no target and
delegate to `vaelii.core`.  They are here so a caller can require this one namespace
and reach the whole surface it needs.

Reads — including `check` / `check-edit`, which answer what `assert` would refuse
and write nothing — plus the four writes the browser performs: `edit` (an
assert/retract batch in one settle), `edit-with-consequences` (the same batch, plus
the belief it moved), `forward-chain`, and `preview` (which stores nothing but applies
a batch and rolls it back, so it holds the single writer).  A remote result is already
EDN-clean (the daemon projects sentex records to maps); a local result is the raw
record, and both answer to the same keys, so a caller handles them identically.
raw docstring

vaelii.browser.catalog

What knowledge bases this process can load, and the lifecycle of loading one.

Everything above the engine assumes it is holding the KB. A browser that lists the KBs available, loads one while you watch, and switches to it needs two things the engine does not have: a description of a KB that has not been loaded yet, and somewhere for a load that takes minutes to run while the pages keep answering. Those are the two halves here.

A source is a KB you could load, as data — a kind, a name, and wherever the content comes from. Six kinds:

kindcontentloader
:corethe CxCore vocabulary head alonevaelii.host.core-context
:starterthe shipped schema-only ontologyvaelii.host.starter
:generatedsynthesized from numbers — types, rules, a fwd mixvaelii.host.io.generate
:corpusa translated sentence corpus (OpenCyc)a foreign reader, :cyc-corpus
:dumpa vaelii export dumpvaelii.impl.io.import
:storean on-disk KB already in vaelii's own formatopened in place

The first three ship in this repo and are always offered. The last three are found: each directory on the search path (VAELII_KB_PATH, else ./kbs and ~/.vaelii/kbs) is probed, and what marks it — a corpus meta.edn, a dump meta.edn, a records/ + index/ pair — decides its kind. A catalog.edn (VAELII_KB_CATALOG, else ~/.vaelii/catalog.edn) names sources outside the search path. Nothing about a machine's paths is baked into the repo.

An entry is a source that has been loaded, or is loading: a KB, a status, and a progress reading the loaders report into (:on-progress, reported by every loader — the corpus reader, io.import/import-dump and io.generate/load-into). The running half of that is not here: a load is a job (vaelii.browser.jobs), which is what gives it a thread of its own, the progress reading, the cancel flag and the report — so an entry carries the job's id and reads its status rather than keeping one. One load runs at a time, since a load claims this process's writer, and cancelling one is cooperative: the loaders have no other safe interruption point, and an import is not a transaction, so a cancelled load leaves the KB holding what had already landed.

A KB is readable before it is finished. activate asks only that an entry hold a KB, so the one arriving can be the one every page reads — a corpus is browsable from its first thousand sentexes, and a store that opens in seconds is browsable while belief is still being rebuilt behind it. What that costs a reader is completeness, not correctness, and active-caveat is what says so.

And a KB can go back out. export-entry! writes a loaded one as an export dump as a job like any other, which closes the loop: a dump written under the search path is a :dump source the moment its meta.edn lands, so exporting and reloading needs nothing outside this namespace.

Unloading never deletes an on-disk KB. A memory-backed entry has its stores cleared (they would otherwise hold the corpus for the life of the JVM); a disk-backed one is closed — the file lock released, the directory left exactly as it was. The same directory can then be loaded again, or opened by another process.

What knowledge bases this process can load, and the lifecycle of loading one.

Everything above the engine assumes it is holding *the* KB.  A browser that lists the
KBs available, loads one while you watch, and switches to it needs two things the
engine does not have: a description of a KB that has not been loaded yet, and somewhere
for a load that takes minutes to run while the pages keep answering.  Those are the two
halves here.

**A source** is a KB you could load, as data — a kind, a name, and wherever the content
comes from.  Six kinds:

| kind         | content                                             | loader |
|--------------|-----------------------------------------------------|--------|
| `:core`      | the CxCore vocabulary head alone               | `vaelii.host.core-context` |
| `:starter`   | the shipped schema-only ontology                    | `vaelii.host.starter` |
| `:generated` | synthesized from numbers — types, rules, a fwd mix  | `vaelii.host.io.generate` |
| `:corpus`    | a translated sentence corpus (OpenCyc)              | a foreign reader, `:cyc-corpus` |
| `:dump`      | a vaelii export dump                                | `vaelii.impl.io.import` |
| `:store`     | an on-disk KB already in vaelii's own format        | opened in place |

The first three ship in this repo and are always offered.  The last three are **found**:
each directory on the search path (`VAELII_KB_PATH`, else `./kbs` and `~/.vaelii/kbs`)
is probed, and what marks it — a corpus `meta.edn`, a dump `meta.edn`, a `records/` +
`index/` pair — decides its kind.  A `catalog.edn` (`VAELII_KB_CATALOG`, else
`~/.vaelii/catalog.edn`) names sources outside the search path.  Nothing about a
machine's paths is baked into the repo.

**An entry** is a source that has been loaded, or is loading: a KB, a status, and a
progress reading the loaders report into (`:on-progress`, reported by every loader —
the corpus reader, `io.import/import-dump` and `io.generate/load-into`).  The running
half of that is not here: a load is a **job** (`vaelii.browser.jobs`), which is what gives
it a thread of its own, the progress reading, the cancel flag and the report — so an
entry carries the job's id and reads its status rather than keeping one.  One load runs
at a time, since a load claims this process's writer, and cancelling one is cooperative:
the loaders have no other safe interruption point, and an import is not a transaction,
so a cancelled load leaves the KB holding what had already landed.

**A KB is readable before it is finished.**  `activate` asks only that an entry hold a
KB, so the one arriving can be the one every page reads — a corpus is browsable from
its first thousand sentexes, and a store that opens in seconds is browsable while
belief is still being rebuilt behind it.  What that costs a reader is completeness, not
correctness, and `active-caveat` is what says so.

**And a KB can go back out.**  `export-entry!` writes a loaded one as an export dump as
a job like any other, which closes the loop: a dump written under the search path is a
`:dump` source the moment its `meta.edn` lands, so exporting and reloading needs nothing
outside this namespace.

**Unloading never deletes an on-disk KB.**  A memory-backed entry has its stores
cleared (they would otherwise hold the corpus for the life of the JVM); a disk-backed
one is *closed* — the file lock released, the directory left exactly as it was.  The
same directory can then be loaded again, or opened by another process.
raw docstring

vaelii.browser.examples

Worked examples of the reasoning the shipped ontology actually does — the data, and the one function that runs one.

Nothing here is a story about the engine. Each example names the sentexes it rests on, and those are looked up in the live KB before anything is claimed: an example whose rests-on sentences are not stored is reported unavailable rather than answered, so switching to another corpus greys the examples out instead of silently showing a verdict computed from vocabulary that is not there. The verdict itself is an ordinary ask / escalate / check, and the proof is why.

Two kinds, and the split is about what the KB ships rather than about presentation:

read-only — no premises. The shipped schema is types, taxonomy, metadata and rules, so everything asked of kinds is answerable with no write at all, and the page computes it on render. This is where the taxonomy, transitiveInArg, disjointness and the predicate meta-ontology live.

sandboxed — premises naming individuals. The starter ships no cast (the fables and their casts live in the test-world), so an example about defaults, joins or refusals has to bring its own, and it writes them into the reader's own sandbox context. Nothing shipped can see in, and the sandbox reset takes the whole thing away.

:expect is what the ontology is supposed to answer, and examples_test asserts every one of them — so the page cannot drift away from the KB it describes.

Worked examples of the reasoning the shipped ontology actually does — the data, and
the one function that runs one.

**Nothing here is a story about the engine.**  Each example names the sentexes it
rests on, and those are looked up in the live KB before anything is claimed: an
example whose `rests-on` sentences are not stored is reported *unavailable* rather
than answered, so switching to another corpus greys the examples out instead of
silently showing a verdict computed from vocabulary that is not there.  The verdict
itself is an ordinary `ask` / `escalate` / `check`, and the proof is `why`.

Two kinds, and the split is about what the KB ships rather than about presentation:

  **read-only** — no premises.  The shipped schema is types, taxonomy, metadata and
  rules, so everything asked *of kinds* is answerable with no write at all, and the
  page computes it on render.  This is where the taxonomy, `transitiveInArg`,
  disjointness and the predicate meta-ontology live.

  **sandboxed** — premises naming individuals.  The starter ships no cast (the
  fables and their casts live in the test-world), so an example about defaults,
  joins or refusals has to bring its own, and it writes them into the reader's own
  sandbox context.  Nothing shipped can see in, and the sandbox reset takes the
  whole thing away.

`:expect` is what the ontology is supposed to answer, and `examples_test` asserts
every one of them — so the page cannot drift away from the KB it describes.
raw docstring

vaelii.browser.jobs

Long work, as jobs: one registry, one progress reading, one cancel.

Three things this process does take minutes rather than milliseconds — filling a KB from a corpus, writing one back out, and joining every rule over everything stored. Each of them wants the same four capabilities, and they are the only four: run on a thread of its own so the pages keep answering, say where it has got to, stop when asked, and leave a report somebody can read afterwards. That shape is here once.

A job is {:id :label :kind :status :progress :started :finished :error :summary :result-url}, plus a cancel flag and the future, which no view carries. submit returns the id; job reads one; jobs lists them, newest first. The caller's work is handed a progress! fn and nothing else: what it records shows up under :progress, and what it throws is how cancellation lands, because a tight assert loop has no other point at which stopping is safe.

One status vocabulary, whatever the job is doing:

:running → :cancelling → :done | :cancelled | :failed

:cancelling is the honest middle: cancel! sets the flag and returns, and the work keeps running until it reaches its next progress report — which, for a phase that reports none (opening a large store scans its whole record log before it says anything), can be a while.

The single writer stays single. :writes names the KB a job writes, or true for one it has not opened yet, and one writing job runs at a time: a second is refused with a message naming the job that holds the writer. Two interleaved writers are not serializable (docs/storage.md, the single-writer contract), and a registry that let two through would be a way around the contract rather than a place to watch it from. writes-kb? is the other half of the same question, asked by identity, so a job filling one KB never blocks a write to another.

Cancellation is cooperative, and for a KB-writing job that is not negotiable. A thread interrupt landing mid-cascade on a durable store surfaces as ClosedByInterruptException and can leave a torn removal, so a job with :writes is flagged and never interrupted however long it takes to notice. A job that writes nothing may say :interruptible? true and be cancelled the hard way as well; the registry checks both, so the two can never be confused for one another. A job's thread is the pool's once its body has unwound, so the hard tier is fenced: the body publishes :released under the job's :monitor and cancel! re-reads it there, which is what stops an interrupt aimed at a job that has already finished from landing on whatever the pool runs next.

A finished job's report outlives the job, for an hour — long enough to read what it did, since the page that would have shown it is usually the page you navigated away from. Nothing unsettled is ever dropped, at any age: forgetting a job is releasing its writer claim, and a thread that is still running is still writing. So a wedged job keeps its place and keeps counting towards the running badge, which is the truth about the process — better than a store two writers took turns on.

Long work, as jobs: one registry, one progress reading, one cancel.

Three things this process does take minutes rather than milliseconds — filling a KB
from a corpus, writing one back out, and joining every rule over everything stored.
Each of them wants the same four capabilities, and they are the only four: run on a
thread of its own so the pages keep answering, say where it has got to, stop when
asked, and leave a report somebody can read afterwards.  That shape is here once.

**A job** is `{:id :label :kind :status :progress :started :finished :error :summary
:result-url}`, plus a cancel flag and the future, which no view carries.  `submit`
returns the id; `job` reads one; `jobs` lists them, newest first.  The caller's `work`
is handed a `progress!` fn and nothing else: what it records shows up under
`:progress`, and what it *throws* is how cancellation lands, because a tight assert
loop has no other point at which stopping is safe.

**One status vocabulary**, whatever the job is doing:

    :running → :cancelling → :done | :cancelled | :failed

`:cancelling` is the honest middle: `cancel!` sets the flag and returns, and the work
keeps running until it reaches its next progress report — which, for a phase that
reports none (opening a large store scans its whole record log before it says
anything), can be a while.

**The single writer stays single.**  `:writes` names the KB a job writes, or `true` for
one it has not opened yet, and **one writing job runs at a time**: a second is refused
with a message naming the job that holds the writer.  Two interleaved writers are not
serializable (docs/storage.md, the single-writer contract), and a registry that let two
through would be a way around the contract rather than a place to watch it from.
`writes-kb?` is the other half of the same question, asked by identity, so a job filling
one KB never blocks a write to another.

**Cancellation is cooperative, and for a KB-writing job that is not negotiable.**  A
thread interrupt landing mid-cascade on a durable store surfaces as
`ClosedByInterruptException` and can leave a torn removal, so a job with `:writes` is
flagged and never interrupted however long it takes to notice.  A job that writes
nothing may say `:interruptible? true` and be cancelled the hard way as well; the
registry checks both, so the two can never be confused for one another.  A job's thread
is the *pool's* once its body has unwound, so the hard tier is fenced: the body
publishes `:released` under the job's `:monitor` and `cancel!` re-reads it there, which
is what stops an interrupt aimed at a job that has already finished from landing on
whatever the pool runs next.

**A finished job's report outlives the job**, for an hour — long enough to read what it
did, since the page that would have shown it is usually the page you navigated away
from.  Nothing *unsettled* is ever dropped, at any age: forgetting a job is releasing
its writer claim, and a thread that is still running is still writing.  So a wedged job
keeps its place and keeps counting towards the running badge, which is the truth about
the process — better than a store two writers took turns on.
raw docstring

vaelii.browser.reload

The development browser's source reloader, which only scripts/start-vaelii-dev.sh turns on (vaelii.browser.web/dev-repl). Before each request it reloads every watched source file that changed since the last request, together with every loaded namespace that requires one of them, transitively, in dependency order.

A protocol, record, type or interface is never redefined. Re-evaluating a defprotocol defines a new interface and empties the protocol's extension map, and re-evaluating a defrecord, deftype or definterface defines a new class, so a KB loaded before the reload would hold values the reloaded code no longer recognizes. A reload therefore loads a file form by form and leaves out each of those forms whose protocol or class already exists; every other form is evaluated. A record keeps its methods inline, where protocol dispatch is a direct interface call, and a method that calls a function reaches the reloaded function through its var. An edit inside such a form is not loaded: restart-owed names it until the process restarts, and the page shows that list. A form for a class that does not exist yet is evaluated.

A held namespace is never re-evaluated at all. A namespace whose ns symbol carries :clojure.tools.namespace.repl/load false holds protocols and method-less records (vaelii.impl.types.*, vaelii.impl.protocols, …). An edit to one reloads nothing on its account, and restart-owed names it.

Form by form, not tools.namespace's refresh. refresh removes each namespace with remove-ns before it loads the file again, which re-evaluates every defonce in it and empties the state a loaded KB keeps there: the cache registry, the change feed's listeners, the thaw guard's installed readers. Loading into the existing namespace keeps a defonce's value, as require :reload does.

Only loaded namespaces reload. A dependent this process never loaded, such as an LLM provider the browser has not reached, stays unloaded: loading it would run code the running server does not use.

tools.namespace ships in the :dev profile only, so its dependency graph and ns-form reader are resolved when wrap-reload builds the reloader, never at this namespace's load. The standalone jar compiles this namespace and has no tools.namespace.

The development browser's source reloader, which only `scripts/start-vaelii-dev.sh`
turns on (`vaelii.browser.web/dev-repl`).  Before each request it reloads every watched
source file that changed since the last request, together with every loaded namespace
that requires one of them, transitively, in dependency order.

**A protocol, record, type or interface is never redefined.**  Re-evaluating a
`defprotocol` defines a new interface and empties the protocol's extension map, and
re-evaluating a `defrecord`, `deftype` or `definterface` defines a new class, so a KB
loaded before the reload would hold values the reloaded code no longer recognizes.  A
reload therefore loads a file form by form and leaves out each of those forms whose
protocol or class already exists; every other form is evaluated.  A record keeps its
methods inline, where protocol dispatch is a direct interface call, and a method that
calls a function reaches the reloaded function through its var.  An edit inside such a
form is not loaded: `restart-owed` names it until the process restarts, and the page
shows that list.  A form for a class that does not exist yet is evaluated.

**A held namespace is never re-evaluated at all.**  A namespace whose ns symbol carries
`:clojure.tools.namespace.repl/load false` holds protocols and method-less records
(`vaelii.impl.types.*`, `vaelii.impl.protocols`, …).  An edit to one reloads nothing on
its account, and `restart-owed` names it.

**Form by form, not tools.namespace's `refresh`.**  `refresh` removes each namespace with
`remove-ns` before it loads the file again, which re-evaluates every `defonce` in it and
empties the state a loaded KB keeps there: the cache registry, the change feed's
listeners, the thaw guard's installed readers.  Loading into the existing namespace
keeps a `defonce`'s value, as `require :reload` does.

**Only loaded namespaces reload.**  A dependent this process never loaded, such as an
LLM provider the browser has not reached, stays unloaded: loading it would run code the
running server does not use.

tools.namespace ships in the `:dev` profile only, so its dependency graph and ns-form
reader are resolved when `wrap-reload` builds the reloader, never at this namespace's
load.  The standalone jar compiles this namespace and has no tools.namespace.
raw docstring

vaelii.browser.sandbox

Somewhere safe to be wrong.

A sandbox is a scratch context of one browser session's own, hung below CxWell so it sees the whole shipped ontology and nothing shipped sees it. A reader can therefore use every type, every relation and every rule the KB ships, and cannot damage any of them: their content is visible only from inside, and one control takes all of it away again.

Why that shape and not a permission system: visibility here is logical, not administrative. genlCx already decides what a context can see, and hanging the sandbox at the bottom of the spindle gives exactly the asymmetry wanted — everything flows in, nothing flows out — with no new concept and nothing to enforce. A shipped rule firing over sandbox facts places its conclusion in the sandbox, because placement is the maximal common descendant of the rule's context and the antecedents' (docs/contexts.md), and the sandbox is the only context below both. So the derived content is inside the thing that gets discarded, without anything arranging for that.

Three facts about the lifecycle:

  • The context is created on the first write, not on the first page. A reader who only looks costs the KB nothing, and a KB full of empty sandboxes would be a KB with a genlCx edge per idle visitor.
  • The session id is in the context name, so two readers of one process never share one. It is minted into a cookie by wrap-session and validated on the way back in — a name is being built from it, and a name built from unvalidated client input is an injection.
  • Reset is a real teardown, not a flag: every sentex in the extent goes through edit's :remove, and the genlCx edge with them. The edge is not in the extent — genlCx is a forced-decontextualized predicate, so it is stored in CxUniverse — which is why it is fetched by hand rather than swept up with the rest.

Promotion — moving something out of a sandbox into a context that outlives it — is deliberately not here. A sandbox is a dead end, and a dead end that cannot be half-escaped is easier to reason about than one with an entry point in it.

Somewhere safe to be wrong.

A **sandbox** is a scratch context of one browser session's own, hung below
`CxWell` so it sees the whole shipped ontology and nothing shipped sees it.  A
reader can therefore use every type, every relation and every rule the KB ships, and
cannot damage any of them: their content is visible only from inside, and one control
takes all of it away again.

Why that shape and not a permission system: visibility here is *logical*, not
administrative.  `genlCx` already decides what a context can see, and hanging the
sandbox at the bottom of the spindle gives exactly the asymmetry wanted — everything
flows in, nothing flows out — with no new concept and nothing to enforce.  A shipped
rule firing over sandbox facts places its conclusion **in the sandbox**, because
placement is the maximal common descendant of the rule's context and the antecedents'
(docs/contexts.md), and the sandbox is the only context below both.  So the derived
content is inside the thing that gets discarded, without anything arranging for that.

Three facts about the lifecycle:

- **The context is created on the first write, not on the first page.**  A reader who
  only looks costs the KB nothing, and a KB full of empty sandboxes would be a KB with
  a `genlCx` edge per idle visitor.
- **The session id is in the context name**, so two readers of one process never share
  one.  It is minted into a cookie by `wrap-session` and validated on the way back in —
  a name is being built from it, and a name built from unvalidated client input is an
  injection.
- **Reset is a real teardown**, not a flag: every sentex in the extent goes through
  `edit`'s `:remove`, and the `genlCx` edge with them.  The edge is not in the
  extent — `genlCx` is a forced-decontextualized predicate, so it is stored in
  `CxUniverse` — which is why it is fetched by hand rather than swept up with the
  rest.

Promotion — moving something out of a sandbox into a context that outlives it — is
deliberately not here.  A sandbox is a dead end, and a dead end that cannot be
half-escaped is easier to reason about than one with an entry point in it.
raw docstring

vaelii.browser.svg

The inline-SVG primitives the term page's concept graph is drawn with: a node, an edge, an arrowhead, and the arithmetic that lays out a row, a column or a ring.

No graph library. The browser ships two JavaScript files and this adds none — a layout that is a fold over a row of boxes is a dozen lines, and a dependency that drew it would be the largest thing the client loads. Nor a shell-out: a page that renders by starting a process is a page that cannot be served.

Everything here is pure — no KB, no access facade, no belief — so it is tested on hand-built maps. What a node means is the caller's: it supplies the term, the colour class, the link and the tooltip, and this decides only where the box goes and what shape it is.

Coordinates live in one flat user space and may be negative; scene crops the viewBox to the union of what was actually drawn, so a sparse graph is centred rather than adrift in a fixed canvas and a long snake_case label is never clipped. Every number reaching an attribute is a long: Clojure's / yields a ratio, and 1/2 in an SVG attribute is not a coordinate.

The inline-SVG primitives the term page's concept graph is drawn with: a node, an
edge, an arrowhead, and the arithmetic that lays out a row, a column or a ring.

**No graph library.**  The browser ships two JavaScript files and this adds none — a
layout that is a fold over a row of boxes is a dozen lines, and a dependency that drew
it would be the largest thing the client loads.  Nor a shell-out: a page that renders
by starting a process is a page that cannot be served.

Everything here is **pure** — no KB, no access facade, no belief — so it is tested on
hand-built maps.  What a node *means* is the caller's: it supplies the term, the
colour class, the link and the tooltip, and this decides only where the box goes and
what shape it is.

Coordinates live in one flat user space and may be negative; `scene` crops the
`viewBox` to the union of what was actually drawn, so a sparse graph is centred rather
than adrift in a fixed canvas and a long snake_case label is never clipped.  Every
number reaching an attribute is a **long**: Clojure's `/` yields a ratio, and `1/2` in
an SVG attribute is not a coordinate.
raw docstring

vaelii.browser.web

A small reitit-ring web browser over a KB:

/ the upper ontology (contexts, types, core predicates, disjointness) /stats KB-wide counts, contexts by size, and the reasoning-health ledgers /find?q=<pattern> the terms whose name matches, from the index's term roster /term?q=<term> every sentex containing the term, grouped by the index root that reaches it (functor / argument-position / context / term-index) /sentex/:id a sentex (literal or rule): its belief state (IN, or the why-not reason — superseded / defeated / unsupported), supports, dependents /justification/:id a justification: its supports (arguments) and dependent sentex /levels?q=<goal> the lookup-to-query stack: what each of the 8 levels answers /edit the multi-sentex editor (GET seeds it, POST saves) — a fragment /{term,find,levels}/rows one more page of a capped list, as bare rows

Run it with lein run -m vaelii.browser.web (serves a starter-loaded KB on :3000). Handlers are pure request -> response, so they are testable without a server.

Every page is answered twice over: as a whole document, and — when htmx asks, which is every navigation and search — as the #main fragment that actually lands. What a page costs in KB reads is part of what this demonstrates, since the browser reads the public surface alone and each read is a round-trip under --attach; see the view section below and docs/web.md.

A small reitit-ring web browser over a KB:

  /                 the upper ontology (contexts, types, core predicates, disjointness)
  /stats            KB-wide counts, contexts by size, and the reasoning-health ledgers
  /find?q=<pattern> the terms whose name matches, from the index's term roster
  /term?q=<term>    every sentex containing the term, grouped by the index root that
                    reaches it (functor / argument-position / context / term-index)
  /sentex/:id       a sentex (literal or rule): its belief state (IN, or the why-not
                    reason — superseded / defeated / unsupported), supports, dependents
  /justification/:id    a justification: its supports (arguments) and dependent sentex
  /levels?q=<goal>  the lookup-to-query stack: what each of the 8 levels answers
  /edit             the multi-sentex editor (GET seeds it, POST saves) — a fragment
  /{term,find,levels}/rows   one more page of a capped list, as bare rows

Run it with `lein run -m vaelii.browser.web` (serves a starter-loaded KB on :3000).
Handlers are pure `request -> response`, so they are testable without a server.

Every page is answered twice over: as a whole document, and — when htmx asks, which
is every navigation and search — as the `#main` fragment that actually lands.  What a
page costs in KB reads is part of what this demonstrates, since the browser reads the
public surface alone and each read is a round-trip under `--attach`; see the `view`
section below and docs/web.md.
raw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close