Repository navigation
v0.4.6
Egatari 1.0 WD 2026-10-24 に従う(§5.6 と、live §3 で足された決まりを除く) / Follows Egatari 1.0 Working Draft 2026-10-24, except §5.6 and the additions to live §3
Added
- Quality check (command and editor): text inside an element with
data-annot(a deliberately small annotation, such as the reading-order numbers ofm-kanbun) is not reported as too small (smallText,smallSvgText). The attribute name lives inANNOT_ATTR(kit/core/layout-scan.mjs); contract §10 is its rule. The mark only counts when a component sets it at mount; the player removes everydata-annotwritten in the deck's own markup first. No other check and no rendering changes. - Colab notebook: an optional cell to use your own reference voice for Irodori (upload a WAV/MP3 of 3-30 s or give a Drive path). It is checked, converted to 48 kHz mono and used for every speaker; without it the built-in voices are used as before. Use only a voice you have the right to use; the file stays in your Colab runtime. Output format and
inputHashare unchanged - Built decks now carry a tab icon: the Egatari mark (the optical 32 px version of
assets/logo) as an inlinedata:SVG<link rel="icon">at the top of<head>. The same single SVG switches to the dark colours underprefers-color-scheme: dark, so it reads on light and dark tabs and is used by browsers that ignoremediaon<link>. It is added to the normal build,--portableand--play-onlyfiles (and the dev preview), needs no network and no change to the Live CSP (img-src data: blob:already allows it) - Turn it off with
<html data-favicon="off">; a deck that has its own<link rel="icon">(orshortcut icon) keeps it and nothing is added. The icon is added only to the built document, so the source files,sourceSha256and the theme hashes do not change - Live room results (draft, format extension
live§3): Import live room results… in the Attachments panel and in Distribute with Live (questions and polls)… adds the results JSON downloaded from the Live server as adataattachment (attachments/live-<room>-<date>.json, title with only the room and date). It is checked against the vendored egatari/live schema (a630478; activities may carry a displaylabel), the 10 MiB limit and the §3.8 content checks; a room that differs from the lesson's live settings is warned about but allowed; the first import shows a one-time privacy notice. The list marks it "Learners' responses" with the number of questions and the tally per poll, shown as text only - Results are never written into play-only copies. Exporting a single file that contains them warns and offers Export without the results;
npm run portable -- attach <dir> <results.json>adds them from the command line,packandbuild --portableprint a warning, and--without-live-resultsleaves them out - Components get
ctx.reset === trueinrenderon the first step entry after the viewer touched them (docs/contract.md §3) - Editor app: a slide list (the Slides tab at the left edge of the preview) with thumbnails of each slide's final step. Add a slide after the selected one from a template (title and body, two columns, figure, section divider, quote), duplicate it (narration copied, audio not), delete it (a dialog names the slide and warns that its narration and audio go too) and reorder by dragging, the ↑ ↓ buttons or Alt+↑ / Alt+↓. The editor assigns new ids, keeps the narration sections in slide order and the audio bound by key, rebuilds and runs the quality check; Undo takes back the last action until the next one or the next save of a single file. The list is a keyboard listbox with live-region announcements
- Template leftovers: the slide list's placeholder narration lines start with a fixed mark
✎and the template text is listed once (kit/core/template-text.mjs). The quality check (npm run checkand the editor) warns about a placeholder line (narrPlaceholder) and template text left on a slide (templateText); TTS (the editor's Audio panel and Colab job,tools/narration-json.mjsfortts/synth.py) and exports (--portable,--play-only, the editor's Egatari-file exports) list them first and still let you go on - Themes can declare their frame geometry with
metricsintheme.json("v": 1; body, conclusion line and vertical columns in stage pixels of 1280×720), inherited field by field throughextends. The quality checker (command and editor) derives the body limits (bodyBottom,bodyRight,tateBodyHead,bodyConclCol,bodyLeft) from it, so a theme that moves these boxes is checked against the right limits; margins, minimum text sizes and title and conclusion lengths do not change. Withoutmetricsthe defaults reproduce the previous limits exactly. Players never use it to draw, andtheme.jsoncounts toward the theme'ssha256(contract §12, reference) themeMetricsDrift(warning, once per deck): the rendered boxes differ from the theme's declaration by more than 1px, or a theme withoutmetrics(or the deck's own<style>) moves them. Unusablemetricsvalues are a build warning (theme-metrics: …, reported asthemeMetricsInvalid) and fall back to the parent's value- The boxes are measured only with the bundled fonts loaded, never with a fallback font; otherwise the comparison is skipped with one
metricsFontLoadwarning node tools/theme-metrics.mjs measure <theme>prints themetricsblock measured from the rendered theme, in whole pixels rounded outward to the more lenient limit (checkcompares it with the declaration).tools/test-theme-metrics.mjsfails when a bundled theme renders differently from its declared (or default) geometry
Changed
- Test and inspection screenshots no longer depend on the time between page load and capture:
kit/browser.mjsgainsstableCapture(page, opts)(hides the talk-mode control bar and toast, waits two animation frames, captures withanimations: 'disabled', caret: 'hide'),prepareForCapture(page),releaseCapture(page)andcaptureUrl(url, mode = 'video').tools/shot.mjs,smoke.mjs,sheet.mjs,comment-shots.mjs,flake.mjsand the seek-determinism checks use it; newtools/test-capture.mjschecks that a talk-mode capture at 0.5 s and 3 s after load is identical - Colab notebook: the packages are pinned in two sets chosen from the CUDA libraries cell 1 finds (one place,
COLAB_PINSintools/make-colab-notebook.mjs):cuda13whenlibcublasLt.so.13is there or there is no GPU (onnxruntime-node 1.30.0, @huggingface/transformers 4.3.1, onnxruntime-gpu and onnxruntime 1.31.0, kokoro-onnx 0.6.1: the set that ran on 2026-10-21),cuda12when onlylibcublasLt.so.12is (onnxruntime-node 1.24.3, @huggingface/transformers 4.2.0, onnxruntime-gpu and onnxruntime 1.26.0, kokoro-onnx 0.6.1), so a CUDA 12 image uses its GPU again. Cell 1 prints the environment (Python, Node, GPU and driver CUDA, the CUDA libraries found, the chosen set and its versions) and cell 3 the installed onnxruntime-node and the providers in use, to include in a problem report. The verified note now says: CPU verified on 2026-10-23; the GPU path failed that day and falls back to the CPU; GPU with the pinned versions not yet verified; Kokoro not yet - Colab: every record's
recipecarriesruntime, the pin set and the execution provider that actually ran (cuda12/cuda,cuda13/cpu, …; after the CPU fallback, not the requested list), for Irodori and Kokoro. The editor's zip import keeps it in the audio manifest'srecipe; it is not part ofinputHash, so existing audio does not go stale - Live player: the pane starts collapsed as a small Live marker at the top right (a button with
aria-expanded), and the slides use the whole window. Tapping the marker opens the notice and Join before joining, and the question box once connected, on any slide. On a step with a poll the pane opens by itself (announced in a live region, without taking the focus) and collapses again when that step is left, unless the learner pinned it (Keep open) or opened it from the marker. While connected the collapsed marker shows the state (a green dot, and text for screen readers); Disconnect is in the opened pane. When open, the pane is docked beside or below the slides as before, so it never covers them. The fold strip is gone; × or Esc collapses the pane. Reduced motion is respected, and the zero-network rules are unchanged - Live player: where it cannot connect (inside a frame such as an LMS, a viewer in a chat or file app, another kind of address), the pane offers Join in the browser, a link that opens the room's join page (
https://<endpoint host>/join#room=…&key=…, the key only in the fragment) in a new tab, with a short explanation and the room. Nothing is loaded before it is tapped. The editor's preview still shows only the room's address as text - Live room: polls are shown by a label such as “Slide 4 'XML の壁', step 1” (「4 枚目『XML の壁』・段階 1」) instead of the raw step key (
s-cjatth:0) in the editor's step suggestions and poll list, with the key in the tooltip. The editor writes it as the optionallabelof each poll (display text, at most 80 characters; keys unchanged), and Copy activities JSON includes it, so egatari/live's teacher and join pages show it too. Older polls get a label when the panel opens if their slide is still in the deck; otherwise, and in rooms without labels, the key is shown ass-cjatth · step 1(*as “whole lesson”). The vendored protocol is updated to egatari/live a630478 - Live screens use plain words: ライブ, 参加者, 待機室, ステップ, 参加コード, ホスト用リンク, ホストのページ, 全員に表示 (in English: Live, participants, waiting room, step, join code, host link, host page, show to everyone). Export ▾ → Distribute with Live (questions and polls)… (before: Distribute with a live room…), Export Egatari file (editable) and Export Egatari file (play only). Strings only; no behaviour changes
- Distribute with Live: To fix before exporting shows plain messages in the editor's language (for example 「投票 p1:設問を入れてください(300 字まで)」) instead of the validator's developer text.
LivePolicy().validatereturns problems as{code, path, …params, text}(textandtoString()are the former English message, so the command line prints the same), andapp/i18n.js(liveProblem) maps each code to ja/en - Editor app: one way to record. The Audio panel's ● Record (r) opens recording mode at the panel's step (Space, speak, Space, Esc);
the panel stays open behind it and then shows the new take. Parity of the old panel recording: docs/recording-parity.md - Editor app: recording mode reports a stop at the 600 s limit, stops there also in a background tab, and reports a recorder that cannot start
- Build: the theme's CSS (
tokens.css, parent themes first) now comes afterkit/layouts.cssin<style id="deck-base">(before: after the fonts and KaTeX, beforebase.css), in every build path (CLI,--devand the dev server, portable and play-only exports, the editor app). A theme rule now overrides a layout rule of the same specificity (the cover title's position, the.bodyinset, the heading rule,.concl,.pn) without anhtmlprefix.latin.cssstays last. The bundled themes (default, business, kokugo) render the same as before (backlog #75) - New optional tokens with the old values as fallbacks:
--marker(list markers; default--term),--barand--bar-h(the progress bar; default--emph, 3px). Proposed with K1 by @totasuko-lab
Removed
- Editor app: the Audio panel's deck-wide list “TTS steps made with an older text pre-processing”. The pre-processing and
recipe.textNormalizestay; old audio keeps playing and is regenerated only with “Regenerate with TTS” - Editor app: recording inside the Audio panel (its button, meter and “Record again”); use recording mode
Fixed
- Colab notebook: when the GPU's CUDA libraries do not match onnxruntime (on 2026-10-23 Colab had no
libcublasLt.so.13, which onnxruntime-node 1.30 needs, and Irodori stopped while loadingtext_encoder), the Node worker now rebuilds the session on the CPU, uses the CPU for the rest of the job and prints one line in Japanese and English with the reason; Kokoro does the same. Only a failure on the CPU as well still stops the job bnow blacks out the screen: a full-window black cover goes over the slide, ink, captions, the control bar and the key help (before, the current slide stayed visible).bagain, Esc, a click, the arrows, Space, PageUp/PageDown or Home/End bring the screen back without moving the slide, a faint “Blacked out — press b or Esc to return” hint shows in the corner for 2 s (and again on mouse move), Esc leaves blackout before it closes the key help, and entering blackout cancels the outside-link box. Print and video mode are unchanged- Keys reach the slides after using the pen or clicking the editor: a click on the slide with the pen or laser on (or on the control bar) moves focus into the deck; in the editor app, after a click on the toolbar, header or status bar, the arrows, PageUp/PageDown, Home/End,
b,aandfgo to the preview (never Space or Enter, and never from a text field, the narration drawer or an open dialog), and toolbar buttons clicked with the mouse no longer keep focus - Editor app: while unsaved narration holds the preview, a click or key on it shows why, with Save and Revert next to it; the first click on the preview after leaving a narration field now focuses the preview
- The outside-link box keeps focus when you click its text, and Esc and Tab work even if focus has left it
- Presenter view: a click on the "next" pane no longer swallows the keys
- Presenter view: Esc (when no drawing tool is on),
por the new Close presenter view (p) button close it and return focus to the main window (in fullscreen they only leave fullscreen), also from the current-slide pane and in the editor app;pin the main window closes an open presenter view; while the window has no focus, a “Click here to control” overlay takes the first click; the key help (?) and the docs give the cmd+Tab / Alt+Tab workaround and the recommended order (presenter view first, then the audience window fullscreen) - Live player: a play-only copy opened on Android from the Files app in Chrome (a
content://address) can now join the live room. It counts as a local file the learner opened, likefile:; the other conditions are unchanged (not framed, inside the window, exactly one CSP<meta>whoseconnect-srcis the endpoint, one tap on Join, not in video, print or the presenter view) - The runtime calls component
mountonly after the stage is laid out: a deck started inside adisplay:noneiframe or a hidden tab waits for the stage to get a size and its fonts to load, then mounts and draws, so figures (e.g.m-kanbun's 書き下し文) look the same as after a normal start;DECK.readycomes after the mount in both cases - Editor app: recording mode shows a failed take's error (an empty or undecodable take, a recorder that cannot start) instead of failing silently
- Printing and PDF: the section divider (
sec) kept its text but lost its background (white text on white paper) unless the print dialog's “Background graphics” was on. Slides and dividers now carryprint-color-adjust: exact(and-webkit-print-color-adjust) inkit/base.css's print rules, so every slide background prints in its screen color; the page itself (html,body) stays white, and the screen rendering is unchanged.tools/test-print-backgrounds.mjschecks the computed styles in print media and the pixels of a PDF made without background graphics (editor)

- Editor app: a Space pressed right after opening recording mode (r or ● Record (r)) is no longer lost. The screen appears and takes the focus at once; until the step is loaded its Space button reads “準備中… / Getting ready…” (
aria-busy, announced in the live region). A press in that time (key or button) is kept and starts one take as soon as the screen is ready (once, even if pressed twice), and is dropped if opening fails (a declined rights check, no narration, Esc). A refused microphone or a recorder that cannot start is reported as before - Editor app: opening recording mode again while it is opening or open (r held down, Space on the still-focused panel button) no longer adds a second recording screen that Esc leaves behind
- Editor app: a Space pressed in the preview at the same moment as r no longer reaches the deck (it advanced the slide)
- State the viewer changes by hand in a component (an open
m-kobunconjugation card) no longer carries across steps: entering any step,
also the same one again (DECK.go,DECK.seek, a slide change and back), redraws a touched component asrender(k, t)(format spec §8.4)
Security
- Dependency updates for two Dependabot alerts. KaTeX 0.16.47 to 0.18.2 (exact pin), which fixes a prototype pollution in KaTeX's settings that could bypass the
trustrestrictions. Egatari builds decks from the author's own source and only trusts\htmlClass,\htmlIdand\htmlData, so exposure was low; formulas in the examples render pixel-for-pixel the same (the generated class names gain akatex-prefix, e.g.baseis nowkatex-base; nothing in Egatari depends on them). certifi 2026.7.22 to 2026.10.19 intts/uv.lock, which drops root CAs whose intermediate keys were compromised - Links that leave the lesson (format spec §11): a click on a link to another site or file no longer navigates. The player shows the destination's host and full address and opens it only on Open, in a new tab with
noopener,noreferrer; it asks every time when the link text names another host, the destination is nothttps:, the link downloads, the address has a user name or password, or the host is punycode, and once per host otherwise.javascript:,data:,file:and every scheme buthttp(s),mailtoandtelare refused. The box is keyboard-operable (focus on Cancel, Tab cycles, Esc cancels) and makes the lesson behind it inert. Middle and modified clicks are covered; video mode does nothing; the editor preview and dev server frame tell where the link goes but open nothing. The rule is a pure module,kit/core/link-policy.mjs(DECK.LinkPolicy), for the future play page - Links the player makes itself (the live room's join link, attachment saves) skip the box, as spec §11 (h) allows: they carry a JavaScript-only mark that lesson markup cannot set, and the live pane now shows the join page's host ("Goes to: …") next to the link
- SECURITY.md and the README: use a Node.js release with the latest security fixes (the 2026-10-21 releases fixed Brotli and TLS certificate checking issues)
editor-site-v0.4.6.tar.gz sha256: 86318a04df97f5bd24604c3cd6ee41b10c0e096878defcbb1de73bc925697140