feat(context): news-style lower-third + sidebar HUD#6
feat(context): news-style lower-third + sidebar HUD#6Kyonax wants to merge 2 commits intofeat-brand_kotfrom
Conversation
Build context-screen as the second @kyonax_on_tech web source — news-style lower-third with marquee row and toggleable right sidebar that renders any .org-authored context with full landing-page control via BroadcastChannel. - pull Geomanist from kyo-web-online + declare --font-display, --surface-bg gradient, and 4 motion tokens on :root - add uniorg-parse runtime dep (~25 KB gzipped, localhost-only) - shared/utils/org.js: AST parser + metadata / marquee / body partitioner (Rule J topic library, OrgSchemaError) - @ui/org-content.vue: recursive Vue template AST renderer; zero v-html (D11 — ESLint no-restricted-syntax bans innerHTML); D10 per-node styling for headlines / lists / checklists / tables / src+results / quotes / links / hr - brand-loader.js: +CONTEXTS glob /@*/data/contexts/*.org + getContexts() helper - @composables/use-context-channel.js: singleton BroadcastChannel composable + debounced localStorage persist + html-class toggle - @kyonax_on_tech/sources/hud/context-screen.vue: full HUD source — strip (Geomanist + SpaceMono), marquee (translateX loop), sidebar (translateX slide + translateY auto-scroll on inner wrapper + custom lateral indicator), peek arrow (layered static halo + animated opacity); contain: layout paint on every sub-tree - @ui/chip.vue: +shape prop (pill | square) for D15 sharp-corner lock - @modals/context-control.vue: clickable slug list + sidebar toggle + live iframe preview; extends BaseModal - @elements/card.vue: conditional CONTROLS button + modal mount - 2 fixture .org files at @kyonax_on_tech/data/contexts/ Modified-by: Cristian D. Moreno (Kyonax) <kyonax25@gmail.com>
Protected Files ModifiedOne or more files in the protected set were changed in this PR. Each category below explains why the file matters. Legal / Licensing
Modifying these files changes the project's legal posture. Confirm with the maintainer before merging. Supply Chain
Dependency or lockfile changes. Verify the diff (no unexpected packages, no version downgrades). Build / Config
Build or gitignore config. Verify the build still passes and no ignored paths were accidentally un-ignored. |
Refine context-screen toward GitHub-flavored alert callouts, a disciplined 60-30-10 palette where gold is reserved for the 10% accent tier, per-page rem-base scaling for OBS readability, and typography parity between sidebar headlines and alert titles. Privatize the contexts/ folder so personal notes stay local. - alerts: NOTE / TIP / IMPORTANT / WARNING / CAUTION via =[!TYPE]= quote-block prefix detection — Octicon SVG icons (info / light-bulb / report / alert / stop, MIT-licensed primer/octicons paths bound via =:d=, never v-html), per-type saturated 100-shade left-bar border + 6% color-mix surface + italic font-display body - alert plumbing: WeakMap-cached =detectAlertType=, AST prefix- strip via shallow clone (no AST mutation), font-display forced on every descendant via =.org-alert :where(*)= + explicit =.org-content= override (the recursive UiOrgContent wrapper would otherwise cascade =font-mono= down) - tertiary palette: +purple family (50–500, hue 266) added to =$colors= in =_variables.scss=; =--clr-tertiary-*= tokens emit automatically through =_theme.scss='s existing =@each= - color rebalance (60-30-10): gold demoted out of headlines / tables / chips into the 10% accent tier (marquee, status dot, headline 3-dot stack, OUTPUT frame, alert borders); body neutrals shifted neutral-50 → neutral-100 globally for a less-light feel - headline accent: replace the 3px gold =|= bar with three stacked 3px gold squares (single 3px square + ±6px box- shadow clones); =padding-left: 0.5em= + =transform: translateY(60%)= so the stack centers on the first text line - code blocks: lang flag bg → primary-400, OUTPUT flag border + text → neutral-300 to mirror the src-block frame, ws markers 2px → 3px (darker neutral-400 / primary-400), =padding-top: 2.5em= so code clears the absolute label; src-block + example get =padding-bottom: 0.4em= - tables: switch to inline =v-for= row/cell render — drop the recursive =UiOrgContent= inside =<tbody>= / =<tr>= because Chromium can mistreat =display: contents= on table-internal divs and collapse columns; =table-layout: fixed= + =word-break: break-word= on cells for narrow-sidebar fit - sidebar/strip chrome: scope the html font-size base to the page via =html:has(.context-screen-overlay) { font-size: 16px }= so every rem-based =--fs-*= renders ~33% larger in OBS without touching the global 12px base; sidebar ratio 0.30 → 0.34; chip override → quiet outline (=fs-225=, neutral-100 text, transparent surface, =--clr-border-100= edge); strip =border-bottom= restored, marquee =border-top= dropped to avoid a 2px stack at the divider - marquee: =v-if= on empty items so the gold strip doesn't render when a context has no =#+begin_marquee= block; separator squares 3px → 6px - verbatim / inline-code: dark-neutral-400 pill + neutral-100 text + =line-height: 1.5em= + =width: fit-content= safeguard - quote: alert-format without header — =border-left: 1px solid var(--clr-border-100)=, italic, no gold left bar - lists: chevron + checkbox markers via flex =align-self: center= + =transform: translateY(0.05em)= (the parent =align-items: baseline= ignores =vertical-align= on flex children); chevron rule generalized to non-ordered lists so descriptive lists also get markers; tighter marker right- margins - title parity: =.org-headline= and =.org-alert__header= share =font-display= / weight 700 / =letter-spacing 0.06em= / uppercase — sidebar section titles and alert callout titles read as the same typography system - privatize contexts: =@kyonax_on_tech/data/contexts/= added to =.gitignore= — personal notes stay local; only the rendering plumbing is tracked. Existing fixtures removed from the index via =git rm --cached= Modified-by: Cristian D. Moreno (Kyonax) <kyonax25@gmail.com>
Protected Files ModifiedOne or more files in the protected set were changed in this PR. Each category below explains why the file matters. Legal / Licensing
Modifying these files changes the project's legal posture. Confirm with the maintainer before merging. Supply Chain
Dependency or lockfile changes. Verify the diff (no unexpected packages, no version downgrades). Build / Config
Build or gitignore config. Verify the build still passes and no ignored paths were accidentally un-ignored. |
Checklist (check if it applies)
npm run lint)Pre-Check Failedlabel applied by CI)What does this PR do?
Adds the second
@kyonax_on_techweb source — a news-style lower-third HUD targeting an OBS CONTEXT scene. Per-video content authors as a single.orgfile under@kyonax_on_tech/data/contexts/<slug>.org, parses to a unified AST at Vite glob-time viauniorg-parse, and renders through a recursive Vue template walker (<UiOrgContent>) covering 18 org node types — headlines, lists, checklists, tables, src-blocks with Shikitokyo-nightsyntax highlighting,#+RESULTS:blocks, quotes, links. State syncs from a new<ContextControlModal>on the landing page to every mounted browser source through a singletonuseContextChannelcomposable that combines same-processBroadcastChannelwith an HTTP polling/push relay over a Vite middleware endpoint — needed because OBS browser sources run inside a separate Chromium (CEF) process whereBroadcastChanneldoes not propagate. 11 new files, 16 modifications, 46 new tests bringing the suite from 33 to 79.Design / Reference: Brand identity tracked via
.github/assets/logo.txt. Marquee animation pattern adapted from the Kyonax siblingkyo-web-online/src/app/scss/components/_marquee.scss. Scope tracked inCHANGELOG.orgTODO block.Implementation
Brand assets (
src/app/,NOTICE)src/app/fonts/Geomanist/GeomanistRegular.ttf— pulled from siblingkyo-web-online; HUD title facesrc/app/fonts/Geomanist/GeomanistBold.ttf— display weightsrc/app/fonts/Geomanist/GeomanistItalic.ttf— italic displaysrc/app/scss/base/_typography.scss— three new@font-facedeclarations via the existingfont-facemixinsrc/app/scss/abstracts/_theme.scss— declares--font-display,--surface-bgtoken, four motion tokens (--motion-sidebar-ms: 280ms,--motion-strip-ease,--motion-peek-pulse-s: 2.5s,--motion-marquee-s), plus shade-tokens--clr-primary-100-{02,03,04,06,10,14,40,80}and--clr-neutral-50-{02,04,12}src/app/scss/abstracts/_mixins.scss— addsalpha-tint($color-var, $percent)/darken/lightenSCSS functions wrappingcolor-mix(); addscorner-dots($color, $size, $corners)background-image mixin and pseudo-elementcorner-square-tl/corner-square-trmixins for the HUD-chrome sharp-corner intersection markersNOTICE— Third-Party Fonts section crediting Atipo Foundry (Geomanist) and Colophon Foundry (SpaceMono)Org parser + AST renderer (
src/shared/utils/,src/shared/components/ui/)src/shared/utils/org.js(158 LoC) — Rule J topic library wrappinguniorg-parse. Named exports:parseOrg,extractMetaKey,extractFiletags,extractMarqueeBlock,collectBodyNodes.OrgSchemaErrorclass for required-field violations;REQUIRED_KEYS = ['TITLE', 'DESCRIPTION']drives the validation loop.src/shared/utils/org.test.js— 14 tests covering happy-path,OrgSchemaError, every named export, both#+TAGS:and:filetag:forms, marquee-absent collapse pathsrc/shared/utils/highlight.js(68 LoC) — async wrapper around Shiki'scodeToTokenswithtokyo-nighttheme. In-memoryMapcache keyed by<lang>:<code>;SUPPORTED_LANGUAGESset coveringjs / ts / vue / html / css / scss / json / bash / python / markdown / yaml / diff; falls back to a single grey-token line for unsupported langs.src/shared/components/ui/org-content.vue(698 LoC) —<UiOrgContent>recursive Vue template AST renderer. Zerov-html(project ESLintno-restricted-syntaxbansinnerHTML). Per-node styling covers 18 node types — headlines (with|rule glyph instead of corner-bracket SVG), paragraphs, bold/italic/verbatim/code/strike, ul/ol/dl, list items with checkbox glyphs, tables, src-blocks with lang label + Shiki tokenized output + 2px whitespace markers via background-image gradient,#+RESULTS:blocks with OUTPUT label, quotes, examples, external links, horizontal rules. Lists usedisplay: flex; align-items: baselineto keep marker + paragraph child on the same line.src/shared/components/ui/org-content.test.js— 11 mount tests via@vue/test-utils mountasserting expected DOM per node type plus a final no-<script>-injection regression checksrc/shared/brand-loader.js— addsCONTEXTSglob/@*/data/contexts/*.org(eager,?raw);getContexts(handle)per-brand helper;buildContextsMap()partition that parses every.orgat glob-time and caches{ raw, parsed, parse_error }src/shared/brand-loader.test.js— 7 new assertions onCONTEXTSshape, per-brand discovery, parsed schema, slug uniqueness,getContextshappy + missing-handle pathssrc/shared/components/ui/chip.vue—+shapeprop (pill | square) with inline validator; both shapes lockborder-radius: 0;squareis the explicit sharp-corner-mandate consumer used by the context-screen sidebar tagsCross-page state plane (
src/shared/composables/,vite.config.js)src/shared/composables/use-context-channel.js(228 LoC) — singleton wrappingBroadcastChannel('reckit:context-screen'). Reactive refsactive_slug+sidebar_open; methodssetActiveSlug/toggleSidebar/hideSidebar. DebouncedlocalStoragepersist (100 ms). Toggles.context-sidebar-openclass ondocument.documentElementviaeffectScope(true)+ watch. Adds an HTTP polling/push bridge to a Vite middleware endpoint (RELAY_ENDPOINT = '/__context_state',RELAY_POLL_INTERVAL_MS = 300) — every action POSTs the snapshot, every consumer GETs every 300 ms; echo-suppression vialast_pushed_hash.src/shared/composables/composables.test.js— 5 new tests across three describe blocks: singleton-identity, initial-state shape + method exposure,BroadcastChannelmock withpostMessagespy onsetActiveSlug+toggleSidebar. Mock implementsaddEventListener/removeEventListenerperunicorn/prefer-add-event-listener.vite.config.js— addscontext_relay_pluginVite plugin.configureServer(server)mounts a middleware on/__context_state:GETreturns the current{ active_slug, sidebar_open }JSON snapshot;POSTreplaces it. State lives in plugin closure for the lifetime of the dev server. Cross-process bridge — OBS browser source's CEF process polls the same endpoint as the landing page, so toggling the sidebar in the landing-page modal updates OBS within ~300 ms p95 even thoughBroadcastChannelcannot cross the process boundary.HUD source (
@kyonax_on_tech/)@kyonax_on_tech/sources/hud/context-screen.vue(599 LoC) — full HUD source. Four surfaces:.context-striplower-third (Geomanist Bold title + SpaceMono description; width transitions 100% to 62% on toggle — the one acknowledged non-transformanimation per D13),.context-marqueerow (gold background, black text,transform: translateX(0% to -50%)infinite linear, items duplicated for seamless loop),.context-sidebarslide-in panel (z-index 100,transform: translateX()+opacity, mounts<UiOrgContent>, nativeoverflow-y: autofor user scroll),.context-peekclosed-state ambient indicator (z-index 99, layered span + animated opacity pulse mirroring<UiStatusDot>'s static-halo + animated-opacity pattern). Strip + sidebar share a single 1px border (border-right: noneon the strip); strip's TR corner-square overflows above the sidebar viaz-index: 110on.context-lower. Auto-scroll constants:OPEN_DELAY_MS = 3000,BOTTOM_HOLD_MS = 3000,SCROLL_SPEED_PX_PER_SEC = 16,MS_PER_SEC = 1000,USER_INTERACTION_PAUSE_MS = 5000(auto-scroll yields for 5 s after any user wheel/touch interaction).@kyonax_on_tech/data/contexts/obs-browser-sources.org— fixture-rich example covering every supported D10 node type (headlines, lists, checklists, table, src-block,#+RESULTS:, quote, link, horizontal rule, marquee block)@kyonax_on_tech/data/contexts/quick-note.org— minimal no-marquee fixture exercising the collapse-to-zero validation path@kyonax_on_tech/sources.js—+context-screenregistry entry,status: 'ready'Landing-page control surface (
src/views/)src/views/components/modals/context-control.vue(533 LoC) —<ContextControlModal>extending<BaseModal>. Two-section grid: clickable slug list on the left (active slug highlighted via<UiBadge variant="active">, parser errors via<UiBadge variant="dim">+ inline message); live preview iframe on the right withgetBoundingClientRect()scaling per existing<PreviewModal>pattern + 100 ms-debounced resize handler. Header brand chip + RELOAD IFRAME footer button.src/views/components/elements/card.vue—+CONTEXT_SCREEN_IDconstant;+is_context_screencomputed;+is_control_openref;+openControl/+closeControlmethods; new.card-secondary-actionsrow holding DETAILS + conditional CONTROLS button;<ContextControlModal>mount conditional onsource.id === 'context-screen'src/views/components/sections/setup.vue— landing-page hover state migrated fromrgba(255, 215, 0, 0.04)literal to the newvar(--clr-primary-100-04)shade token (token-migration polish surfaced during the context-screen styling pass)src/views/components/sections/sources.vue— same shade-token migration for hover/border statessrc/views/components/modals/preview.vue— same shade-token migration for the preview overlay backdropDependencies
uniorg-parse@^3.2.1,shiki@^4.0.2—uniorg-parsepullsunifiedplus a small unist transitive set;shikiships pre-bundled grammars and thetokyo-nighttheme. Combined ~70 KB gzipped contribution to the index chunk; acceptable on localhost-only browser-source runtime per§1.14budget reasoning (no network roundtrip, OBS Chromium loads once at scene start).package-lock.jsonupdatedTechnical Details
BroadcastChannel + HTTP relay over OBS WebSocket bus
useContextChannelcombining same-processBroadcastChannelwith an HTTP polling/push bridge over a custom Vite middleware (/__context_state).BroadcastChannel;import.meta.hot.sendHMR custom events.BroadcastChannelonly crosses tabs inside the same Chromium process, so a landing-page tab and an OBS source never see each other's broadcasts. The Vite-middleware HTTP bridge gives a universal cross-process channel that works during dev (the only environment that matters for a localhost-only app), is debuggable withcurl http://localhost:5173/__context_state, and frees the OBS WebSocket budget (perCONTRIBUTING.org) for video state.import.meta.hot.sendwas tried first and silently failed in CEF.BroadcastChanneldelivers); the bridge only works while the Vite dev server is running, so this design is dev-mode-only — production builds would need a different transport. Acceptable: this app is a localhost streaming rig, not a deployed service.uniorg-parseAST + custom Vue rendereruniorg-parseas a runtime dep; parse.orgto a unified AST at Vite glob-time; walk the AST inside a new<UiOrgContent>primitive.uniorg-rehypeto generic HTML; abandoning.orgfor.json/.md.#+RESULTS:, multi-section bodies, quotes, links — overshoots the break-even point of a custom parser.uniorg-parseis battle-tested; bundle cost is irrelevant on localhost.Recursive Vue template walk, never
v-html<UiOrgContent>walks the AST through a recursive<template>v-forovernode.childrenwith one branch per node type. Inline text via mustache; code via<pre><code>{{ value }}</code></pre>(textContent-bound).v-htmlwithuniorg-rehypeto HTML; manualdocument.createElementinsideonMounted; string templates with manual escaping.eslint.config.mjsbansinnerHTMLassignment viano-restricted-syntax(XSS hardening).v-htmlcompiles toinnerHTML, so it's effectively forbidden. The recursive-template approach is also faster (Vue's reactivity tracks individual nodes), more secure, and gives per-node styling control for free since each node is a real DOM element with its own classes.v-html. Trade accepted for security + styling control.Shiki
tokyo-nightsyntax highlighting in src-blocks#+BEGIN_SRCblock via Shiki'scodeToTokensAPI on first render; cache the tokenization result per<lang>:<code>key in a module-levelMap; render tokens as styled<span>elements inside<pre><code>.highlight.js.tokyo-nightmatches the cyberpunk aesthetic without per-element CSS overrides. Async tokenization yields the main thread on initial render so the lower-third strip + marquee paint immediately while code colors arrive on the next microtask. Cached per-block so re-renders hit zero cost. Whitespace inside src-blocks is also visualized via a per-segmentsplitWhitespaceSegmentshelper that emits<span class="org-src-block__ws">nodes painted with a 2px-tall background-image gradient at 1ch tile size — the marker color is per-block (--clr-neutral-300for src,--clr-primary-300for#+RESULTS:).Hybrid 3-surface layout: lower-third + marquee + right sidebar
widthtransition is the SINGLE non-transform/ non-opacityanimation in the source — a one-time-per-toggle ~280 ms layout reflow. Amortised cost across a recording session (typically 2 toggles).Sidebar auto-scroll via
body.scrollTopwrite with user-interaction pauseOPEN_DELAY_MS(3 s) writingbody.scrollTop += SCROLL_SPEED_PX_PER_SEC * delta_seconds(~16 px/s) on the sidebar body; bottom-hold 3 s; reset and loop. Wheel/touch handlers updatelast_user_interaction_at; the tick pauses forUSER_INTERACTION_PAUSE_MS(5 s) after any interaction, then resumes from the user's manual scroll position.transform: translateY()on an inner wrapper insideoverflow: hidden(the original D13 plan); CSSscroll-behavior: smooth; one-shotElement.scrollTo; ignoring the user.transform-on-wrapper saves layout cost in theory but the sidebar's content surface is the only updating sub-tree andcontain: layout paintis set on the consumer.scrollTopwrites do trigger layout per frame inside the contained sub-tree — measured fine on the test rig. The hot path is otherwise zero-allocation per§1.14.6(delta computed viaperformance.now(), single DOM write, no new objects).Closed-sidebar peek indicator with layered halo + animated opacity
box-shadow: var(--hud-halo-text)(never animated); inner<span class="context-peek__pulse">owns the breathingopacityvia@keyframes; root fades to opacity 0 on the.context-sidebar-opentoggle.animation-composition; JS-driven opacity tick.<UiStatusDot>idiom — onlyopacitychanges per frame; the dark shadow stays cached. Single composited property.Reuse existing
@ui/primitives, expand only via prop<UiChip variant="solid" shape="square">; active-slug indicator via<UiBadge variant="active">; modal shell via<BaseModal>; iframe scaling via the samegetBoundingClientRect()pattern as<PreviewModal>. The only NEW primitive introduced is<UiOrgContent>. The only existing primitive expanded is<UiChip>(one newshapeprop with inline validator).<UiOrgChip>/<UiSquareChip>/<UiSidebarHeader>; inline pill markup directly inside<UiOrgContent>; override chip CSS via context-screen-scoped overrides.<UiChip>— minimal surface-area expansion vs a new file.Corner-square HUD chrome via SCSS pseudo-element mixins
corner-square-tl($color, $size: 4.5px)/corner-square-trSCSS mixins that paint pseudo-elements at the corner intersection — half outside, half inside the box — via negativetop/left|rightoffsets. Strip uses both TL + TR; sidebar uses TL only (the canvas-edge corners get no marker per design).corner-dotsmixin); positioned<i>elements inside each surface; SVG accents.position: relativeitself: the consumer must already be a positioned element; setting it inside the mixin clobbered the sidebar'sposition: absolute; right: 0pattern (caught by smoke test).::beforeand::afterand must not usecontain: paint(paint clips pseudo-elements rendered outside the box). Documented in the mixin's header comment.Testing Coverage
Test runner: Vitest 4.1 + Vue Test Utils 2.4 (happy-dom environment)
Command:
npm run testAutomated tests
src/shared/utils/org.test.js#+TAGS:and:filetag:forms, marquee block, body partition,OrgSchemaErrorfor missing required keyssrc/shared/components/ui/org-content.test.js<script>-injection regressionsrc/shared/composables/composables.test.jsuseContextChannelsingleton-identity, initial state + method exposure,BroadcastChannelpostMessagespy onsetActiveSlug+toggleSidebarsrc/shared/brand-loader.test.jsCONTEXTSshape, per-brand discovery, parsed-schema, slug uniqueness,getContextshappy + missing-handleit.each)src/shared/version.test.jsTotal: 79 tests across 5 files, all passing in 826 ms (was 33 / 357 ms before this PR).
Quality gates (run on every PR)
eslint.config.mjsvianpm run lintvite.config.jsvianpm run testvite.config.jsvianpm run build.github/workflows/ci.yml.github/workflows/ci.ymlPre-Check Failedlabelpre-check-labeljob in.github/workflows/ci.ymlHow to test this PR
Setup
npm run devExpected: Vite serves on
http://localhost:5173; the relay middleware at/__context_stateis mounted (verify withcurl http://localhost:5173/__context_state, expect{"active_slug":null,"sidebar_open":false}).http://localhost:5173/Expected: Landing page renders. The Sources grid contains a
CONTEXT-SCREENcard alongsideCAM-LOG.CONTEXT-SCREENcard, clickCONTROLS.Expected: A modal opens with two sections — slug list on the left, live preview iframe on the right. Two slugs visible:
obs-browser-sources,quick-note.Lower-third + marquee always-visible
obs-browser-sourcesslug.Expected: Slug row gains the active highlight and an
ACTIVEbadge. Preview iframe shows the lower-third strip (Geomanist Bold uppercase title, SpaceMono description on the right, single 1px border with sharp corners and 4.5px white corner-squares overflowing the TL + TR intersections) and the marquee row below it (gold background, black text, 1px-then-3px square separators between items, scrolling right-to-left at the tokenized cadence).Expected: Zero
border-radiusrounding anywhere on the strip, marquee row, or peek indicator.Sidebar toggle from the landing page
Expected: Within ~280 ms, the preview iframe shows: strip width shrinks to ~62%, sidebar slides in from the right (
translateX+opacity), peek arrow fades to opacity 0. Strip + sidebar share a single 1px border at their boundary (no doubling); strip's TR corner-square overflows above the sidebar viaz-index: 110..org's tags as<UiChip shape="square">instances.Expected: Sharp corners on every chip; uppercase tag text in SpaceMono; chips wrap to a second line if needed without breaking the header layout.
Expected: Sidebar slides out, strip reflows to 100% width, peek arrow fades back in (breathing pulse opacity 1 to 0.55 over 2.5 s). All transitions complete in ~280 ms with the same easing curve.
Org rendering coverage (Shiki + per-node styling)
Expected: Body renders the parsed AST: H2 / H3 headlines with a
|glyph in the title's color (NOT a corner-bracket SVG); paragraphs in SpaceMono; bold / italic / verbatim / inline-code with their respective per-node styling; a checklist with▢/▣glyphs; an unordered list (each item flush with its bullet — no line-wrap break between marker and paragraph child); a table with thin borders + monospace cells; a#+BEGIN_SRC jsblock with Shikitokyo-nightcolors, lang label in the upper-right, and 2px whitespace-marker squares at every space/tab; a#+RESULTS:sibling block with a black "OUTPUT" header label, gold border, and gold whitespace markers; a#+BEGIN_QUOTE; an external link in primary color.Expected: Each space / tab visualised as a 2px tall × 1ch wide background dot at vertical center. Src-block dots are
--clr-neutral-300;#+RESULTS:dots are--clr-primary-300.Sidebar auto-scroll + user-interaction pause
Expected: Sidebar body begins auto-scrolling downward at ~16 px/s.
Expected: Holds 3 s, then resets to top (instant snap), then resumes the loop.
Expected: Auto-scroll yields immediately. After 5 s without further interaction, it resumes from the new position.
Closed-state peek indicator
Expected: A small
.context-peekelement with the❮glyph is pinned to the right edge, vertically centered. Inner pulse breathes opacity 1 to 0.55 over 2.5 s ease-in-out infinite. Outer halo (box-shadow: var(--hud-halo-text)) is visible but not animated.Expected: Peek arrow fades to opacity 0 over 280 ms, sync'd with the sidebar slide-in.
Cross-process bridge (HTTP relay)
curl http://localhost:5173/__context_state.Expected: JSON snapshot reflects the modal's current
active_slugandsidebar_open..org(missing#+DESCRIPTION:) at@kyonax_on_tech/data/contexts/broken.org. Save.Expected: Slug appears in the modal's slug list with the error variant +
ERRORbadge + theOrgSchemaErrormessage inline. No console error in the dev server.http://localhost:5173/@kyonax_on_tech/context-screendirectly.Expected: The standalone HUD page reflects the same
active_slug+sidebar_openas the modal within ~300 ms p95. Toggling the sidebar in the modal updates this tab even though the two tabs are in the same browser process (validating the relay path).OBS smoke test (deferred — user-only, requires OBS Studio)
http://localhost:5173/@kyonax_on_tech/context-screen, width 1920, height 1080, FPS 60.http://localhost:5173/in a normal browser tab; clickCONTROLSon the context-screen card; pick a slug; toggle the sidebar.Expected: OBS Browser Source receives the same state as the landing-page iframe within ~300 ms via the HTTP relay (validates that the bridge crosses CEF process boundary). Sidebar slides in, marquee scrolls, peek arrow fades. fps in OBS Stats panel holds at the canvas target with no encoder or render lag introduced.
Documentation
DIAGRAM — Three-surface HUD layout
DIAGRAM — Cross-process state plane
DIAGRAM — Org parser pipeline
VIDEO — Sidebar slide-in + auto-scroll + interaction pause
SCREENSHOT — Lower-third + sidebar open with full org rendering