Add an md-print element that paginates markdown into A4 landscape sheets holding two A5 pages, each with configurable columns. The preview modal exports through the browser print dialog.
Paragraphs, lists, and tables split at word, item, and row boundaries; headings are kept with following content.
Replace the flat dropdown lists with 3x3 icon grids. Alignment uses
arrow icons for the 8 edge/corner positions and a center-align icon
for the middle cell; orientation places its 4 arrows on a cross. Cells
use fixed-width tracks so they no longer collapse inside the
shrink-to-fit dropdown.
Document that the CLI mounts the content directory at the URL root and
that the app uses History API routing, not the old /content hash scheme.
Replace the removed <Article> iconPath prop with the actual upward
assets/ lookup, and fix the deck_preview preview_url example.
The CLI content server serves the content directory at the URL root, so
images and other content assets are requested as root-relative paths
(e.g. /adventures/map.png). The dev proxy only forwarded /content, so
those requests fell through to historyApiFallback and the browser got
index.html instead of the asset.
Forward any request that maps to a real file under content/ to the CLI
server, keeping /static/ bundles on the web dev server.
Add a header toggle and a panel close button so the desktop file
sidebar collapses with a width transition, mirroring the journal
panel.
- split onClose (close button) from onNavigate (file selection) so
clicking a file no longer collapses the sidebar on desktop
- give the aside h-full so the file tree and outline keep bounded
scroll regions inside the new wrapper
- swap the aside shadow for a gray right border to match the journal
Swap emoji and unicode glyphs for Tabler components so icons inherit
currentColor and font-size and render consistently across platforms.
- add src/components/doc-icons.ts to resolve doc-entry frontmatter icon
keys to components; frontmatter now stores semantic keys
- replace the align-*.png assets with TbOutlineAlign* icons
- render RevealManager action SVGs as components instead of injected
innerHTML strings
- carry completion icons via an optional CompletionItem.icon field
Validate the id extracted from ttrpg/+/stream and ttrpg/+/meta before it is used to build a path, closing a traversal where a .. topic level (or ..\.. on Windows) could escape the sessions directory and delete the whole data root. Meta writes now go through the schema, so malformed payloads are ignored instead of deleting.
Deletion was also a no-op: an empty payload threw in JSON.parse before the delete branch ran, so sessions were never removed. Check for an empty/null payload before parsing. The worker validates ids and surfaces publish/connection failures.
Session ids become directory names server-side and topic levels on the broker, so they must reject path separators, ./.., whitespace and Windows reserved device names. toSessionId derives a safe id from a display name; isValidSessionId is the boundary check shared by the CLI and the web client.
The broker connection now persists across session changes. Joining or switching a session swaps subscriptions instead of reconnecting, and a lobby lets sessions be listed and created before joining one.
Fixes session switching being a no-op (the store only updated local state) and session creation being unreachable before connecting. Also removes the dead ConnectionBar and the no-op hydrateFromServer/setSessionId store calls.
Replaces the central manifest.json with per-session meta.json so the filesystem is the single source of truth. Metadata writes are now atomic (temp file + fsync + rename), startup adopts orphan dirs, corrupt meta is quarantined instead of silently wiped, truncated stream tails are repaired, and appends to unknown sessions are dropped so deleted sessions cannot be resurrected.
Existing manifests are migrated to per-session metadata on first run.
Add the bare d-form (d20) to the dice notation table, and note that role fences accept backticks or tildes with hyphen-capable attribute keys. Regenerate both reference copies from known.ts and mirror the notes in the skill and markdown docs.
Fenced block scanning was backtick-only in FENCED_BLOCK_RE (used by scanDoc) and in the yaml role=tag render extension, while stripFencedBlocks and the validator already accepted tildes. Match both marker styles and require the closing fence to reuse the opening marker.
The key pattern \w+ could not cross a hyphen, so data-config=foo was parsed as config=foo and the data- prefix was lost. Allow hyphens in keys, matching parseDirectiveAttrs.
The tokenizer only recognized dice when the pattern started with a count (3d6), so a bare d20 fell through to the identifier branch and failed as an unknown identifier. Add a dedicated branch for the bare d-form; DICE_RE already accepted it.
topoSortAffected followed declared dependencies even when they were not in the affected set, so reevaluateDependents re-evaluated and re-published variables that had not changed. This could also overwrite a value just set via /set with the declaration's own expression.
The journal now owns command history and tracking, so retire the md-commander widget. Its dice engine was extracted to src/components/dice in the previous commit.
Removes the custom element, tracker/command stores, completions, useCommander, Emmet parser, the doc entry, test fixtures, and the known-element catalog entry (reference docs regenerated).
BREAKING CHANGE: :md-commander is no longer a known element; content using it now fails tttk check with TTTK-D001.
The dice engine lived under md-commander/hooks but is shared by :md-dice, spark tables, the journal /roll command, and dice inside variable expressions. Move it to src/components/dice so the md-commander widget can be retired without losing rolling.
Also promote the browser-console harness to a real jest suite; the file was named test.ts, so jest never ran it.
Update the journal-set doc entry, the CLI diagnostic table, and the
authoring skill, along with the in-app empty-state hint and the var
message comment.
Replace the tag/threshold/key/expr CSV body with YAML `vars:` and
`tags:` maps. `parseDeclare` validates the body with an exported Zod
schema and returns the same declaration and modifier shapes the
reactivity engine already consumes.
The breaking rename from `parseDeclareCsv` updates the registry, the
journal re-exports, and the `tttk check` role checks, where R003 now
reports schema errors with their path and R004 warns when a block
declares nothing.
EvalContext.lookup now receives the full $var name, matching getCombined,
setBase, and getMods, and parseFactor throws "Unknown variable" when the
lookup returns undefined. /set and /roll use the new resolveVariable() so a
typo errors instead of silently evaluating to 0.
Declarations and tag modifiers stay lenient (unknown -> 0): the documented
$hp = $con*5+$mod_hp pattern depends on external/undeclared variables
defaulting to 0.
Make the reactivity engine re-initialize when the loaded declarations
change instead of only at import. ensureReactivity() reads the live
completions signal, compares a signature, and rebuilds from the store
(with exact bases) only when needed; dispatchCommand calls it before each
dispatch. Remove the unused DispatchContext.declarations/tagModifiers
fields, which were mount-time snapshots and could not be trusted.
Build initReactivity's maps into locals and commit only after the
circular check passes, so a failed re-init leaves the previous engine
intact; clear inFlight on commit.
Drop the unreachable var "del" action from the schema, render, and
reducer.
Collect and report failed declarations, failed tag modifier expressions, and
circular dependencies instead of swallowing them. Diagnostics are deduped,
logged with key/expression context, cleared on re-init, and exposed through
useJournalCompletions and the VariableView warning banner. A bad declaration
no longer aborts the rest of a cascade.
Reject malformed numeric literals in the tokenizer so inputs like "1.2.3"
throw instead of being truncated by parseFloat.
The stream only carried combined values, so hydrating a session lost the raw base and rebuildReactivityFromStore re-applied tag modifiers on top of already-combined values (e.g. $hp 70 became 90).
Publish the raw base alongside the combined value on var messages, persist it in variableBases, and rebuild engine state from exact bases. Derive tag activations from base tagmaps so tagmap-valued modifier targets are not re-triggered on every rebuild.
Seed declared variables only after hydration and only for keys absent from the store, so persisted values are no longer clobbered on load and reloads stop appending duplicate var messages.
The spark message never displayed the dice, and its rollResult came from a
second, independent roll that could disagree with the row actually
selected.
rollSparkTable now reports the roll(s) it used, so the message can show
'header / notation -> roll' in the purple box and render each rolled value
as markdown (its own div) below it.
Inline spark tables (role=spark-table) live in the content registry, but the
rsbuild dev proxy forwarded only /__COMPLETIONS.json and
/__CONTENT_INDEX.json. The registry fetch fell back to the SPA index.html,
so /roll resolved the inline CSV as HTML and csv-parse failed with an
invalid opening quote.
Load the registry before publishing the loaded completions state so a roll
cannot fire while inline content is still unresolvable.
Add dev:web (rsbuild) and dev:cli (tttk serve ./content), with dev
running both via scripts/dev.mjs, which shuts both down when either
exits. Also add tttk:inspect for --inspect debugging.
Both servers previously defaulted to port 3000, so the CLI could not
bind while the web dev server was running and the proxy targeted
itself. Pin the web dev server to 5173; the CLI keeps 3000 to match
the proxy. Document the two-process setup and inspector debugging in
docs/development.md.
Directive examples sat bare in the doc bodies, so rendering them as
markdown executed the directives: live components, missing-file
fetches, and style side effects on the dialog. Wrap each example in
a markdown fence, matching the convention already used by md-table.
DocContent rendered the raw body in a whitespace-pre-wrap div, so
markdown syntax and fenced code blocks appeared literally. Parse
through the shared markdown pipeline and style with prose.
Wrap the content in an <article> so md-bg and md-border, which
resolve element.closest("article"), get a valid style target
instead of throwing.
Move the skill to .agents/skills/tttk-authoring and point gen:skill-reference at it. Activate it by symlinking ~/.agents/skills/tttk-authoring to the in-repo copy.
Extend the reference drift guard to cover the skill copy now that it is versioned, and document activation in docs/development.md.
Enrich known.ts with element/role details, inline specs, and dice notation, and render them from reference.ts. Add gen:skill-reference and ~ expansion in --out so the skill copy can be regenerated.
The skill's reference.md is now generated; its extra authoring content moved to layout.md, yaml-tag.md, csv.md, and markdown.md.
Add D002 (malformed attribute blocks), R004 (skipped declare rows), and V001-V005 (CSV data files). CSV checks run once per referenced file and also cover inline spark-table bodies.
Generate docs/reference.md from the known.ts catalog via npm run gen:reference, and add drift guards asserting the catalog matches the customElement call sites and the committed reference.
Add check to the CLI guide, README, QWEN, and project structure: options, exit codes, diagnostic codes, and the checks not yet implemented. Mark compile as an unimplemented stub and fix stale element syntax in examples.
Statically validate a directory of tttk documents and report structured diagnostics with file/line positions, exit codes, and --json output. Checks cover code-block roles, unresolved content references, tag blocks, element names, links, anchors, and assets. Runs over the same content registry as the app.
Drop the mermaid import from the markdown module. The extension only emits <pre class="mermaid">, so initialize mermaid in the app entry instead. Use explicit .js specifiers plus a bundler extensionAlias so both the NodeNext CLI build and the web build resolve the sources.
Replaces the render-first compile plan with a diagnostics-first design for a new tttk check command.
Covers the whole-tree registry pass, raw-source diagnostics with line numbers, the rule catalog, and exit codes, plus resolved decisions on the element catalog, mermaid refactor, icon search scope, and CSV checks.
- deriveLinks strips fenced code blocks so markdown examples don't
produce phantom link completions
- Add resolveContentEntry returning body + path + inline flag;
scanDocDirectives uses it instead of re-deriving resolution, and
parseDirectiveAttrs is replaced by the shared parseBlockAttrs
- Fix relative file refs with ./ prefix never resolving through
resolveContent (pathIndex lookup joined the ./ verbatim)
- Drop resolveBlockAs and the as= attribute; scanDoc now switches
directly on role and warns on unknown roles instead of silently
stripping
- md-table derives data-spark from its loaded CSV via
parseSparkTableCsv; delete injectSparkDirectives and the second
registry pass so pathIndex is exactly scanDoc output
- Align spark-table dice header regex with isSparkTableHeader