Repository navigation
v0.13.0
Added
-
cargo teksilo— agent tooling for apps that depend on Teksilo. An agent
working inside this repository has the guides, the worked examples, the skill
and the automation harness. An agent working in someone's Teksilo app had
none of them:docs/ships in no crate, every example crate is
publish = false, and the only probe harness lived in one app's repository.
cargo install cargo-teksilocloses that gap, and every answer is matched to
the Teksilo the app actually resolved.-
cargo teksilo symbol <Name>— the exact public API of a type, read from
the resolved sources. Works with a crates.io, git or path dependency and
needs no checkout: where one is reachable it runs that checkout's own
extractor, otherwise it stages a throwaway repository shaped like this one
around the registry sources. A bare name resolves against every teksilo
crate, socargo teksilo symbol ListModelanswers with teksilo-data's
without--crate data; the note on stderr says which crate answered, and
names the others when more than one defines that name. Types declared
directly in a crate'slib.rsare included — teksilo-webview'sWebView
among them. This is the one subcommand that needspython3on PATH. -
cargo teksilo search "<question>"— retrieval over the 70 hand-written
guides and 56 worked example crates, which reach no consumer today. Hits are
ranked and carry no score: neither ranker produces a confidence, and the
lexical and hybrid numbers are not in the same unit. -
cargo teksilo show <path>— the document behind a search hit, in full,
offline:cargo teksilo show docs/scroll-area.md, or just the lines the hit
cited (--lines 166-172, 1-based and inclusive, assearchprints them).
Nothing is re-fetched — a chunk already carries its own text and the line
range it occupied, so the document is reassembled from the corpus, byte
for byte. It closes the last version-binding hole in the tool: the path a
hit cites exists in no consumer's project, and fetching it fromblob/main/
or the published book servesmainrather than the version the app pinned.
searchnow says so in a footer line, and--listprints every path. -
cargo teksilo probe— writes the automation probe harness into
scripts/teksilo_probe/, so an agent can drive the running app and assert
on it. Generated files are checksummed: a local edit is reported rather
than silently overwritten, and your own probes live outside the generated
tree and are never touched. -
cargo teksilo setup— the above, plus the Teksilo briefing for every
coding agent this project already configures, each in that agent's own
format: the full four-file skill into.claude/skills/teksilo/where it
is native, and a self-contained ~40-line brief into
.cursor/rules/teksilo.mdc(MDC frontmatter),.windsurf/rules/teksilo.md
(trigger:frontmatter),.github/copilot-instructions.mdandAGENTS.md
(each a<!-- BEGIN teksilo -->region) where it is not. These tools share no
format, so copying the skill directory into.cursor/would accomplish
nothing; the brief never refers to the skill, which on those machines is
not installed. It also pre-fetches the search encoder (~129 MB, into the
per-user cache,--no-modelto skip), so the firstsearchdoes not stall
on a download — and a failed fetch is a warning, becausesearchdegrades
to BM25 by design.Three things it now refuses to do. It never writes
$HOMEfrom project
scope — the previous version silently installed into~/.claude/skills/
when the project had no.claude/of its own, a machine-wide change from a
project-scoped command;--useris now the only path that reaches the home
directory. It never prompts where nobody can answer: with no terminal on
stdin the question is an error naming the flag that would have skipped it
(-y, or--userwhen there is noCargo.tomlanywhere above the working
directory), because CI and agents run this and a hang is worse than a
failure. And it never writes before showing the plan — every path, every
detected vendor, and the download with its size, then a confirmation. Runs
are idempotent: a second run reportsunchangedand leaves the shared files
byte for byte, including whatever the project wrote outside the markers.Cline is served too, and is the one vendor whose path is read off the
disk rather than fixed:.clinerules/teksilo.mdwhere that directory
exists, a marker region where.clinerulesis a plain file — which it may
be, and which has nowhere to put a file of our own — and
.cline/rules/teksilo.mdwhere that is the only layout present.The order is deliberately not newest-first. Cline's source calls
.clineCLINE_CONFIG_DIRand.clinerulesDEPRECATED_CONFIG_DIR, so
the newer path looks like the obvious target; but the VS Code extension was
hardcoded to.clinerulesand ignored.cline/rules/outright
(cline/cline#14186), the cross-surface fix reachedmainonly in September
2026 and is in no released build, and Cline's own docs still say its Rules
panel creates new workspace rules in.clinerules/. Preferring the modern
name would install, in the most-used Cline surface, a file nothing reads —
which this tool holds to be worse than installing nothing, because it
reports success. Reachability beats recency, and a test says so, so that a
later tidy-up does not quietly invert it.No frontmatter in any layout: Cline's docs say a rule without one is always
active, so its absence is what keeps the brief unconditional.Two things it refuses to do, both found by adversarially reviewing the
command against itself and reproduced before being fixed. It will not
rewrite a shared file it cannot read whole:AGENTS.mdand
copilot-instructions.mdbelong to the project, and reading them with
read_to_string(..).unwrap_or_default()turned "cannot decode" into "the
file is empty" — one Latin-1 byte was enough forsetup -yto replace a
project's rules with nothing but its own region, silently, reporting
success. It now stops and names the file. And its stray-pruning compares
filenames case-insensitively: the skill is written through
fs::writebut audited throughread_dir, and on APFS or NTFS those
disagree about case, so an exact comparison classified a file it had just
written as a leftover and deleted it — leaving a skill with noSKILL.md
while reportingupdated. Folding can only err towards keeping a stale
file, which is recoverable; deleting a live one is not.In user scope it reaches three agents — Claude Code (
~/.claude/skills/),
Mistral Vibe (~/.vibe/AGENTS.md, or$VIBE_HOME) and opencode
(~/.config/opencode/AGENTS.md, or$XDG_CONFIG_HOME) — each gated on
its directory already existing, so a home directory gains no config
directory for a tool that was never run. Both environment overrides are
honoured: Vibe relocates its entire state directory throughVIBE_HOME, so
writing the default path for a user who moved theirs would leave a file
nothing reads. What was not found is reported with the path that was
checked, not with a label that would send a relocated install looking in
the wrong place. The rest have nowhere to go, which is likewise reported
rather than silently narrowed: Cursor's user rules are edited in its
settings UI, Copilot's personal instructions live on github.com, Windsurf's
global rules are one file at
~/.codeium/windsurf/memories/global_rules.md, and a repository-root
AGENTS.mdis per-repository by definition.
cargo teksilo statusreports what is actually installed — every agent, in
both the project and the user scope, plus the search model, the Python 3
interpretersymbolneeds, and where each sits on disk. It reads only, and
its exit code never depends on what it finds.It is the dry run of
setup, not a second opinion: every agent row comes
fromsetup::inspect, the read-only twin of the function that decides
whether a write is needed, and the two share their content computation. A
test pins the equivalence for every form —inspectreports current exactly
whensetupwould reportunchanged— because a status that computed
"installed" its own way would eventually disagree with the command it claims
to predict, and the disagreement would surface as a user following advice
that does nothing.Three words (
here/not here/n/a) and a reason beside each, because
three words cannot carry the difference between Cursor is not used in this
project and Cursor is used here and has no brief, and that difference is
the whole of what to do next. An install from an older release readshere,
since it is being read right now, with "from another release" in the detail —
calling it anything else would be false. Andn/aalways says why: a bare
one beside Windsurf would read as "Windsurf has nothing", when in fact it
keeps a global file this tool declines to write.Its own command rather than
setup --status: it reports on both scopes while
setupis scope-selected, so the flag would have to mean something the
unflagged command does not. Keeping the read-only thing out of a writing
command's flag space also leaves no--status -yto reason about."Reads only" is enforced rather than asserted. It asks cargo for the
resolved version through--locked, because plaincargo metadata
resolves — it creates a missingCargo.lockand rewrites a stale one,
which a command whose headline claim is that it touches nothing must not
do. A project without an up-to-date lockfile therefore gets an honest
"unknown" rather than a lockfile it never asked for. Paths reaching the
report from$VIBE_HOME,$XDG_CONFIG_HOMEor the working directory have
their control characters escaped, so a newline in one cannot split a row
and let its tail pose as another agent's line.Version binding is the design constraint, not a detail. The tool reads the
app'sCargo.lock, and a minor or major mismatch refuses instead of
answering — serving 0.12 answers to an app on 0.9 is worse than serving
nothing, becauseSplitViewwas deleted outright in favour ofSplitter
between them and the wrong answer reads exactly like the right one. A
patch-level difference warns and proceeds.What a refusal can tell a model to run depends on which side of this
release the app sits on, becausecargo-teksilois the first crate in this
workspace without version parity across the framework's history: it did not
exist before 0.13.0, and no tag before that carries a
crates/cargo-teksilodirectory.cargo-teksilo-fmtis the contrast — it
has been published at every teksilo version since 0.9.0, which is exactly why
a--version <app>line can be emitted there unconditionally and cannot be
emitted here. So there are three regimes. Below 0.13.0 no install command
exists at all, and the refusal says so and says to move the app forward;
printing routes that cannot work is worse than printing none, because the
model spends its turn on them and then falls back on its own memory of the
API anyway, which is the single failure this tool exists to prevent. At
0.13.0 or above both routes print unconditionally —--version <v> --lockedfrom the registry, orcargo install --path <checkout>/crates/cargo-teksilo --lockedfrom the framework tree — because
whether a given version reached crates.io is not observable offline, and an
app pinning teksilo bypathorgitresolved a version that was never
published at all. An unparseable version gets the checkout routes, which
are the ones that do not depend on the registry. Where routes print, the
refusal also names the two ways to keep apps on different minors working at
once — installing the second with--root <dir>and putting that
<dir>/binfirst on PATH for that tree, or running the tool straight out of
a checkout withcargo run -p cargo-teksilo. -
-
teksilo-corpus— the guides and the worked examples, chunked into a
retrieval index and published per release socargoresolves the corpus
matching an app's Teksilo. It is one file: a chunk carries its own text, and
itspathnames the original in this repository (docs/scroll-area.md,
examples/simple_button/src/main.rs), so a search result cites something that
opens. Data only; it carries no ML dependency. A guide's closing navigation
footer — "See also", "Reference", "Code references" — is carried under its own
kindand excluded from retrieval rather than from the corpus:show
reassembles a document from its chunks, so dropping one truncates the file,
while indexing one lets a short list of links outrank the prose it points at. -
The automation probe harness, written into a consumer's project by
cargo teksilo probe. Python, stdlib only, embedded in the binary rather than
published to an index — so it is version-matched by construction, lands in the
repository where an agent reading that repository can see it, and needs no pip
or virtualenv. It supplies the JSON-RPC client every probe previously
hand-rolled, a tool surface generated fromTOOL_CATALOG(so it cannot drift
from the bridge), and the virtualized-view navigation rules that are otherwise
rediscovered one misdiagnosis at a time: an off-screen row has no AT node, a
row scrolled back into view is a new widget with a new id, and clicking a row's
reported bounds below the viewport hits empty chrome. Three worked examples
ship with it and run against this repository's own example apps in CI. -
Three new guides: Agent tooling documents the
above; Scroll areas is the reference the docs did not
have — scrolling was covered only by an architecture chapter and the
kinetic-scroll physics, so an ordinary "how do I make this scrollable?" had
nothing to land on; and Docking layout is the consumer
guide forDockingLayout, which had only a generated catalog page and a
design note written for reviewers rather than for users. Those gaps were found by
running retrieval tests over the corpus, not by reading the table of contents.
The design note is gone; its unstarted work is now
Horizontal activity rail (backlog), which
the retrieval index leaves out — a proposal written in the instruction voice
reads as shipped API to whatever retrieves it, and this one outranked the
guide's own activity-rail section. -
tools/extract_widget_api.pynow covers every crate with a public API
(30, up from 4), including the types a crate declares in its ownlib.rs.
mdBook catalog generation stays scoped to the four cataloged crates, so
docs/is unchanged; the rest are queryable through--crate,--list,
--alland by name. -
cargo-teksiloships a README, so a crates.io reader learns what the
semanticdefault feature pulls in, that--no-default-featuresis a fully
working tool, where the model cache lives, and thatsymbolneeds
python3— none of which was readable outside this repository.
Changed
Breaking changes
OverlayDismissCallbackisRc<dyn Fn(DismissReason, &mut EventContext)>,
where it wasRc<dyn Fn()>. A closure that wants neither gains two ignored
parameters —Rc::new(move |_, _| …). The new#[non_exhaustive]
teksilo_core::overlay::DismissReasonnames the route that closed the
overlay:Escape,OutsidePress,PointerLeave,Cascadeor
Programmatic. BothOverlayRequest::on_dismissand
ModalRequest::on_dismisscarry the new type. The fiveOverlayManager
dismissal methods that do not name a reason —dismiss,dismiss_top,
dismiss_all,dismiss_exceptanddismiss_with_focus_restore— keep the
signatures they had and reportProgrammatic; theirdismiss_because,
dismiss_top_because,dismiss_all_because,dismiss_except_becauseand
dismiss_with_focus_restore_becausetwins take the reason instead.- Dismissing an overlay straight through
OverlayManagerno longer runs its
on_dismiss. Code reaching throughWidgetTree::overlay_manager_mut()to
dismiss callsWidgetTree::dismiss_overlayinstead. The ordering is
unchanged — the callback still runs during dismissal, before focus returns to
the trigger. MessageBoxResult::dismissed_by_escapeis replaced bydismissal.
MessageBoxDismissalis#[non_exhaustive]and names the route —Button,
Escape,ClickOutsideorProgrammatic— where the boolean could only say
Escape or not, while its own rustdoc claimed to cover a click outside that no
code path could produce.result.dismissed_by_escapebecomes
result.dismissal == MessageBoxDismissal::Escape; where the flag was read as
"the user did not choose a button", readresult.was_dismissed().button
is unchanged, and still carries the escape-button resolution on every route.
Fixed
- A screen reader can open a
Dialogthat has a custom trigger. The
trigger published a namedRole::Buttonnode that advertised no actions at
all and answered none, so assistive technology could see the control and not
press it — while a mouse worked. This reached the ordinary
Dialog::new(label), not only an explicitly hand-built trigger, and the same
shape affectedSnackbarand aPopoverWidgetover a custom trigger. The
node now advertisesAction::Clickand acts on it. - A modal presented as a native OS window honours its
ModalCloseBehavior.
Escape did nothing to it on macOS and Windows — including in the default
configuration, whereDialogasks forEscapeOrClickOutsideand the
presentation resolves to a real window. The click-outside half stays
unavailable there, deliberately: the OS blocks the parent window while the
modal is up, so there is no outside to click, andClickOutsidenow means
the same asManualfor that presentation. Linux and the BSDs were never
affected — they present modals in-tree, where Escape already worked. - A
MessageBoxdismissed with Escape or a press outside reports its
answer. The dialog closed and the application was told nothing —
on_resultran only when a button was pressed, so a "Save changes?" prompt
could be waved away and leave the caller with no idea whether to save. This
is the in-tree presentation, which is whatModalPresentation::Autoresolves
to on Linux and the BSDs — every unix but macOS — and so was the default
behaviour there;ModalPresentation::InTreereaches it on any platform. Both
routes now resolve the escape button and report it, with
MessageBoxResult::dismissalnaming which one closed the dialog, and a
button that has already answered is not reported over. Distinct from the
native-window fix above, which made Escape close such a window; this makes
the closing report. - An
InputDialogdismissed without a button press reports the
cancellation.Noneis this dialog's cancellation payload, so a dismissal
that reported nothing was indistinguishable from a dialog still sitting open
— against the type's own contract, which promises the callback runs exactly
once when the user accepts or cancels. Escape, a press outside and an
application-driven dismissal now all deliverNone. The in-tree presentation
only: a natively presentedInputDialog— macOS and Windows — still reports
nothing when Escape closes it, andMessageBoxreported correctly on that
presentation already. - A
CommandPaletteruns itson_dismisswhen the palette is dismissed.
The hook fired only when a command was actually invoked, so a caller that
installed it to release whatever opening the palette reserved was never told
about the two commonest ways of closing the palette.CommandPalette
presents in-tree on every platform, so every platform was affected. It now
runs on every route, and once only.
Full Changelog: v0.12.1...v0.13.0