Skip to content

feat(v0.3): Vue app, CAM-LOG overlay, landing index, shared widgets#1

Merged
Kyonax merged 3 commits intodevfrom
websocket-cam-person
Apr 15, 2026
Merged

feat(v0.3): Vue app, CAM-LOG overlay, landing index, shared widgets#1
Kyonax merged 3 commits intodevfrom
websocket-cam-person

Conversation

@Kyonax
Copy link
Copy Markdown
Owner

@Kyonax Kyonax commented Apr 15, 2026

Checklist (check if it applies)

  • Contains testing instructions
  • Requires environment / credential changes
  • Requires special deployment steps
  • Has unit / integration tests
  • Touches licensing, security, or CI
  • License headers on new files (LICENSING.org)
  • Attribution preserved on modified files
  • Lint passes (npm run lint)
  • Security rules followed (SECURITY.org)
  • No credentials committed
  • All GitHub Checks have passed
  • CHANGELOG.org updated (release PRs only)
  • Version bumped (release PRs only)

What does this PR do?

Cuts RECKIT v0.3 — the first end-to-end working build — across three commits on websocket-cam-person: the Vue 3 + Vite app with the CAM-LOG overlay and landing index, a full CI gate suite (lint, security scan, license headers, unit tests, and a Pre-Check Failed label sync), and the widget / version / tooling scaffolding that holds it together.

As a content creator running OBS, I want a Vite-served app that exposes every RECKIT browser source under a per-brand URL and a live index page, so that I can add overlays to OBS with a copy-paste URL and see them reflect my scene, mic, and recording state in real time.

Design / Reference: ASCII logo at .github/assets/logo.txt.

Implementation

[NEW] new file · [MOD] modified file · [DEL] removed · [MOV] renamed or relocated

Bootstrap (src/)

  • [NEW] main.js — Vue app mount point
  • [NEW] App.vue — root <router-view />, loads global SCSS
  • [NEW] router.js — brand-routed routes, 404 catchall
  • [NEW] shared/config.js — frozen OBS_CONFIG from VITE_* env
  • [NEW] shared/version.js — exports VERSION + VERSION_TAG from __APP_VERSION__

Tests (src/**/*.test.js)

  • [NEW] shared/version.test.js — semver shape, VERSION_TAG derivation
  • [NEW] shared/data/overlays.test.js — registry schema, unique ids, status vocab, use_cases[] type, em-dash ban

Styles (src/app/scss/**)

  • [NEW] src/app/scss/** (10 files) — 7-1 SCSS architecture
    • $colors, $typo-scale (x25 steps, 100–800)
    • hud-label-base, cyberpunk-glow mixins
    • brand theming via --clr-* CSS custom properties
  • [NEW] src/app/fonts/SpaceMono/ (4 TTFs) — SpaceMono Nerd Font (Regular, Bold, Italic, BoldItalic)

Composables (src/shared/composables/)

  • [NEW] use-obs-websocket.js — singleton OBSWebSocket, auto-reconnect, subscribes to All | InputVolumeMeters
  • [NEW] use-recording-status.jsRecordStateChanged + take counter + HH:MM:SS formatter
  • [NEW] use-audio-analyzer.js — consumes InputVolumeMeters, defaults to Mic/Aux, 8× gain, 16 bars
  • [NEW] use-scene-name.jsCurrentProgramSceneChanged

Components (src/shared/components/)

  • [NEW] corner-bracket.vue — SVG corner with configurable position, size, stroke_width
  • [NEW] hud-frame.vue — 4 corner brackets + 4 labels + slot
  • [NEW] overlay-card.vue — landing card with **bold** emphasis parser, use_cases[] chip tags, 2-col spec grid
  • [NEW] preview-modal.vue — iframe scaled to canvas aspect ratio via CSS custom property on window.resize
  • [NEW] recording-timer.vue — REC + dot turn red while recording; MODE + time stay white
  • [NEW] status-indicator.vue — blinking dot (red when active, dimmed white otherwise)
  • [DEL] audio-visualizer.vue — superseded by audio-meter widget

Widgets (src/shared/widgets/)

  • [NEW] audio-meter.vue — self-contained OBS audio visualizer
    • props: obs, source_name, bar_count
    • emits update:state with { active, source, peak }
  • [NEW] live-readout.vue — text display with optional refresh_ms sampling throttle

Overlays (src/brands/kyonax-on-tech/)

  • [NEW] cam-person.vue — CAM-LOG HUD at /@kyonax_on_tech/cam-person
    • dynamic top-right label SES::<scene>::T<NN>
    • <audio-meter> bound to Mic/Aux
    • <live-readout> diagnostic line (WS:… | AUDIO:… | L0:…, 200ms throttle)

Overlay registry (src/shared/data/)

  • [NEW] overlays.js — CAM-LOG (ready) + ITEM-EXPLAIN (planned) with SHOW / HIDE / CYCLE triggers declared

Landing (src/views/)

  • [NEW] home.vue — index with sticky frosted meta bar, brand tabs, responsive 3-col card grid, search + status chips, preview modal

Release

Version: v0.3 (was v0.1)

  • [MOD] package.json"version" bump
  • [MOD] README.org#+VERSION:, ASCII logo footer, and shields.io badge bumped to v0.3
  • [MOD] CHANGELOG.org — new [v0.3] entry; TODO refreshed

CI & Tooling

  • [NEW] index.html — Vite entry; MPL header added before DOCTYPE (HTML5-safe)
  • [NEW] vite.config.js — one config for both Vite and Vitest
    • define: { __APP_VERSION__ } sourced from package.json via createRequire
    • test.environment: 'happy-dom', test.globals: true, test.include: src/**/*.{test,spec}.{js,mjs}
    • v8 coverage provider with HTML + text reporters
  • [MOD] .github/workflows/ci.yml — complete CI gate pipeline
    • Triggers: pull_request runs on every target branch (not just master / dev); push fires on feat-* / feat/** / feature/** / fix-* / fix/**
    • Concurrency: group keyed on head_ref || ref with cancel-in-progress: true dedups push-vs-PR double runs and kills stale runs on rapid pushes
    • ESLint: npm ci (lockfile now tracked); lint runs on **/*.{js,mjs,ts} minus node_modules / dist
    • Security Scan: --exclude=eslint.config.mjs + --exclude-dir={node_modules,dist,.cache}; emits ::error file=,line=:: annotations + [category] file:line :: content log lines
    • License Headers: widened to *.vue / *.scss; echoes [MISSING HEADER] <path> per hit + end-of-run summary
    • Unit Tests: new job — npm ci && npm run test (Vitest)
    • Pre-Check Label: final aggregator job, if: always(), reads each gate's result via needs: and toggles the Pre-Check Failed GitHub label with gh pr edit --add-label / --remove-label
    • Permissions: contents: read, pull-requests: write, issues: write (required for label sync)
  • [MOD] .gitignore — re-organized by concern; secret-file extension bans (*.pem, *.key, id_rsa*, etc.); !.env.example negation; package-lock.json un-ignored
  • [MOD] eslint.config.mjs__APP_VERSION__ registered as readonly global; Vitest globals (describe, it, test, expect, beforeEach, afterEach, beforeAll, afterAll, vi) enabled on *.test.{js,mjs} / *.spec.{js,mjs} / __tests__/**
  • [MOD] package.jsontest / test:watch / test:coverage scripts
  • [NEW] .env.example — committed template for the OBS WebSocket vars

Dependencies

  • Runtime added: vue@^3.5.13, vue-router@^4.5.0, obs-websocket-js@^5.0.6
  • Dev added: vite@^6.0.7, @vitejs/plugin-vue@^5.2.1, sass@^1.99.0, vitest@^4.1.4, @vue/test-utils@^2.4.6, happy-dom@^20.9.0
  • Upgraded:
  • Removed:
  • Lockfile: package-lock.json now tracked (0 vulnerabilities)

Docs

Technical Details

  • Audio source pipeline

    • Chose: OBS WebSocket InputVolumeMeters event
    • Over: Web Audio API getUserMedia
    • Why: getUserMedia does not work inside CEF browser sources; OBS gives mixer-accurate levels
    • Trade-off: Requires OBS WebSocket to be running on port 4455
  • Reactive state sharing

    • Chose: Module-level singleton for useObsWebsocket
    • Over: Per-component composable instance
    • Why: Every composable needs the same socket; multiple connections would duplicate events and waste resources
    • Trade-off: Cannot mock the connection per component in tests
  • Array reactivity across props boundary

    • Chose: ref with a new array assignment per frame
    • Over: shallowRef + triggerRef
    • Why: shallowRef + triggerRef does not propagate to child v-for re-renders across the props boundary
    • Trade-off: Allocates a new array object each analyzer frame (~60/s)
  • Preview iframe scaling

    • Chose: Direct CSS custom property mutation (--iframe-scale) on window.resize
    • Over: ResizeObserver-driven reactive computation
    • Why: The observer approach created feedback loops where the scale change triggered a resize event that re-triggered the observer
    • Trade-off: Bypasses Vue reactivity — must remember to update manually if stage size changes outside window.resize
  • Version string distribution

    • Chose: Vite define: { __APP_VERSION__ } reading from package.json at config time
    • Over: Hardcoding the version string in each consumer
    • Why: Single source of truth; bumping package.json automatically propagates to every UI surface
    • Trade-off: __APP_VERSION__ must be declared as a readonly global in eslint.config.mjs and imported via src/shared/version.js
  • Self-contained widgets vs. primitive components

    • Chose: Two folders — src/shared/widgets/ (self-contained, brand-agnostic drop-ins) and src/shared/components/ (primitives that still need wiring)
    • Over: One shared/components/ folder for everything
    • Why: Widgets let future brands import <audio-meter> without learning the analyzer composable API
    • Trade-off: Developers must pick the right folder; naming rules are in the folder README-by-example
  • Overlay description highlighting

    • Chose: **bold** markers parsed into <strong> via String.prototype.matchAll
    • Over: v-html + sanitizer / CMS rich-text field
    • Why: XSS-safe by construction (no HTML parsing); trivial to write in the overlay registry
    • Trade-off: Markdown features limited to bold — no italics, links, or inline code

Testing Coverage

Test runner: Vitest @ 4.1.x with happy-dom and @vue/test-utils
Command: npm run test

Automated tests

Test file Covers Tests Status
src/shared/version.test.js VERSION semver shape, VERSION_TAG derivation (v<major>.<minor>) 3
src/shared/data/overlays.test.js registry non-emptiness, unique ids, required-field schema, status vocab, use_cases[] type, path format, canvas dims, no em dashes 14

Total: 17 tests across 2 files, all passing.

More coverage (composables, widgets, landing computed logic) is tracked as follow-up in CHANGELOG.org under TODO > INFRASTRUCTURE > TESTING.

Quality gates (run on every PR)

Gate Source Status
Lint eslint.config.mjs via npm run lint ✅ 0 errors (6 documented false-positive warnings on numeric array indexing in use-audio-analyzer.js)
Unit tests vite.config.js via npm run test ✅ 17 passing
Build vite.config.js via npm run build ✅ clean (~1.0s)
Security scan .github/workflows/ci.yml ✅ dangerous-pattern / hardcoded-secret / insecure-URL scans pass
License headers .github/workflows/ci.yml *.js, *.mjs, *.html, *.css, *.vue, *.scss all have MPL header
Pre-Check Failed label pre-check-label job in ci.yml ✅ label absent (all gates green)

How to test this PR

v0.3 release
├─ Setup
│   └─ clone · npm ci · npm run dev
│
├─ Landing page
│   └─ meta bar · search · status chip · modal · copy
│
├─ CAM-LOG in OBS
│   └─ scene · take · recording · audio · offline · version
│
└─ CI sanity
    └─ lint · build · workflows

Setup

Prereqs: repo cloned; Node ≥ 20.

  1. Install deps and start the dev server — git checkout websocket-cam-person && npm ci && npm run dev
    Expected: Vite boots on localhost:5173 with no errors in stdout.
  2. Copy .env.example to a local env file and populate the OBS WebSocket values.
    Expected: The local env file is listed as ignored by git status.

Landing page

Prereqs: dev server running.

  1. Open the index — http://localhost:5173/
    Expected: Sticky meta bar shows SOURCES 2 · BRANDS 1 · READY 1 · CANVAS 1920 × 1080 @ 60; header tag reads RECKIT v0.3.
  2. Resize the viewport from ≥ 1200px down to ≤ 400px.
    Expected: Card grid flows 3 columns on desktop, 1 column on mobile; ASCII logo stays centered.
  3. Type vlog into the search input.
    Expected: CAM-LOG card matches via its use_cases[] tags; ITEM-EXPLAIN card is hidden.
  4. Click the PLANNED status chip.
    Expected: Only ITEM-EXPLAIN is visible; the chip dot is brand-gold only on the active chip.
  5. Click OPEN on the CAM-LOG card.
    Expected: Preview modal opens with the overlay iframe scaled to 16:9; pressing ESC or clicking outside closes it.
  6. Click COPY on the CAM-LOG card.
    Expected: URL http://localhost:5173/@kyonax_on_tech/cam-person is in the clipboard; button flashes COPIED.

CAM-LOG in OBS

Prereqs: OBS ≥ 32.1.1; WebSocket enabled on port 4455; Mic/Aux audio input unmuted.

  1. Add a Browser Source — URL http://localhost:5173/@kyonax_on_tech/cam-person, Width 1920, Height 1080, Custom CSS empty, Shutdown when not visible OFF, Enable Browser Source Hardware Acceleration ON.
    Expected: HUD renders over transparent background with corner brackets, crosshair, identity block, and toolkit-id visible.
  2. Rename the current OBS scene.
    Expected: Top-right HUD label updates to SES::<newSceneName>::T01.
  3. Start recording, then stop recording.
    Expected: Timer counts in HH:MM:SS; REC text and status dot turn red while recording; MODE: and the time stay white.
  4. Start recording again.
    Expected: Take counter increments to T02.
  5. Reload the Browser Source.
    Expected: Take counter resets to T01.
  6. Speak into the Mic/Aux input.
    Expected: Audio bars respond in real time; debug line reads WS:ON | AUDIO:Mic/Aux | L0:<nonzero> sampling at 5fps.
  7. Stop OBS or kill the WebSocket.
    Expected: Auto-reconnect attempt every 5s; OFFLINE HUD label appears; debug line reads WS:OFF | AUDIO:NONE | L0:0.
  8. Check the bottom-right of the HUD.
    Expected: Reads RECKIT v0.3.

CI sanity

Prereqs: feature branch pushed to origin.

  1. Run lint locally — npm run lint
    Expected: 0 errors; 6 known false-positive warnings on security/detect-object-injection in use-audio-analyzer.js.
  2. Run build locally — npm run build
    Expected: Clean build; dist/assets/ populated.
  3. Watch PR checks on GitHub.
    Expected: ESLint, Security Scan, License Headers all green.

Special Deployment Requirements

  1. OBS environment (CRITICAL) — OBS Studio ≥ 32.1.1 with CEF browser sources and WebSocket enabled on port 4455.
  2. Audio routing (CRITICAL)Mic/Aux input exists and is unmuted; Desktop Audio and browser-source inputs are skipped automatically.
  3. Lockfile (REQUIRED) — use npm ci for reproducible installs; never npm install in CI.
  4. Release tag (REQUIRED) — after merge to master, run git tag -a v0.3 -m "RECKIT v0.3" && git push origin v0.3.
  5. GitHub Release notes (OPTIONAL) — publish a release page using the [v0.3] section of CHANGELOG.org as the body.

Documentation

DESKTOP — Landing page

Index view at 1440px with 3-column card grid and sticky frosted meta bar.

MOBILE — Landing page

Same view collapsed to a single column at 375px.

VIDEO — CAM-LOG overlay running in OBS

Scene change, recording start, Mic/Aux input, recording stop — captured in OBS.

SCREENSHOT — Preview modal

CAM-LOG iframe rendered inside the landing page modal.

Ship the first fully working build of RECKIT as a Vue 3 + Vite app.
Routes per brand, a live CAM-LOG overlay wired to OBS WebSocket, a
landing page index of all browser sources with search and preview,
and a shared-widgets split so future brands can drop in audio /
readout primitives.

- Vue 3 + Vite 6 + Vue Router 4 + obs-websocket-js 5 + sass at src/
- SCSS 7-1 architecture (abstracts / base / layout / components),
  x25 typo scale, hud-label-base mixin, brand theming via
  CSS custom properties
- Composables: use-obs-websocket (singleton), use-recording-status,
  use-audio-analyzer, use-scene-name
- CAM-LOG at /@kyonax_on_tech/cam-person: HUD frame, dynamic
  SES::<scene>::T<NN> label, recording timer, audio meter bound to
  Mic/Aux, live diagnostic readout
- Landing page at /: sticky frosted meta bar, brand tabs, responsive
  3-column card grid, name/size/tags/requires search filter, status
  chips, preview modal with canvas-aspect iframe, postMessage
  trigger buttons
- Shared widgets (src/shared/widgets/): audio-meter (self-contained
  OBS visualizer) and live-readout (text display with refresh_ms
  throttle) — drop into any brand, theme via --clr-primary-100
- Overlay card: **bold** emphasis markers in descriptions parsed
  without v-html; use_cases[] keyword tags rendered as chips and
  included in the search haystack
- Version centralization: package.json is canonical; Vite injects
  __APP_VERSION__ at build time; src/shared/version.js exports
  VERSION + VERSION_TAG; UI imports VERSION_TAG (no hardcoded v0.x)
- .gitignore hardened (editor state, OS junk, secret-file
  extensions, build caches, !.env.example negation for template)
- README + package.json bumped to v0.3; CHANGELOG [v0.3] entry

Modified-by: Cristian D. Moreno (Kyonax) <kyonax25@gmail.com>
@github-actions
Copy link
Copy Markdown

Protected Files Modified

The following protected files were changed in this PR:

  • CHANGELOG.org was modified

Please ensure these changes are intentional and have been reviewed carefully.

Three CI jobs were failing on the v0.3 release PR. Fix each
root cause and improve error reporting so future failures pin
to a file and line instead of printing bare messages.

ESLint (npm ci failure):
- Regenerate package-lock.json against package.json@0.3.0 via
  `npm install --package-lock-only` (318 packages pinned,
  0 vulnerabilities).
- Remove package-lock.json from .gitignore. It is now
  committed; new comment warns not to re-add it. This is what
  `npm ci` requires.

Security Scan (false positive in eslint.config.mjs):
- The dangerous-call grep was matching the literal string
  'Use DOM APIs instead of document.write().' inside the
  ESLint rule that *bans* document.write — the rule's own
  description was tripping the rule. Add
  --exclude=eslint.config.mjs to the dangerous-pattern scan
  (the config legitimately documents the banned patterns).
- Add --exclude-dir=node_modules/dist/.cache guards so the
  scan behaves identically whether or not build output
  exists.
- Emit real `::error file=<path>,line=<N>::` annotations per
  hit so errors pin to the exact source location on the PR
  diff. Also print human-readable
  `[category] file:line :: content` lines to the raw log so
  the failure is obvious without expanding annotations.

License Headers (no file path in the raw log):
- Echo `[MISSING HEADER] <file>` per hit and print a summary
  block at the end listing every failing path. Annotations
  also pin to `line=1` for inline display.
- Extend the check to *.vue and *.scss for coverage parity
  with the rest of the source tree.

Headers added to unblock the widened license check:
- index.html: MPL block before DOCTYPE (HTML5-safe —
  comments before DOCTYPE do not trigger quirks mode).
- eslint.config.mjs: MPL block prepended so
  `Cristian D. Moreno` lands on line 2 (was on line 7,
  outside the first-5-lines window the check reads).
- 10 SCSS files (src/app/scss/main.scss + every 7-1 partial):
  MPL block prepended to each.

Verified locally with CI-equivalent filters: dangerous-calls
grep clean, license sweep clean, `npm run lint` 0 errors,
`npm run build` clean (~980ms).

Modified-by: Cristian D. Moreno (Kyonax) <kyonax25@gmail.com>
@github-actions
Copy link
Copy Markdown

Protected Files Modified

The following protected files were changed in this PR:

  • CHANGELOG.org was modified

Please ensure these changes are intentional and have been reviewed carefully.

@Kyonax Kyonax added Release The PR is doing a Version bump Special Deployment The PR has Special Deployment steps labels Apr 15, 2026
@Kyonax Kyonax self-assigned this Apr 15, 2026
Three independent-but-related improvements to the CI pipeline.

1. Unit-test infrastructure. Vitest wired into the existing
   Vite config with happy-dom and @vue/test-utils. Two
   initial test files cover the version pipeline
   (VERSION / VERSION_TAG derivation) and the overlays
   registry schema (required fields, unique ids, status vocab,
   use_cases[] type, em-dash ban, path format, canvas dims).
   17 tests total. ESLint taught about Vitest globals on
   *.test.{js,mjs} / *.spec.{js,mjs} / __tests__/**.

2. Pre-Check Failed label sync. New aggregator job at the
   end of ci.yml reads every prior gate's result via needs:
   and toggles the GitHub "Pre-Check Failed" label with
   `gh pr edit --add-label` / `--remove-label`.
   `if: always()` ensures the label syncs even when a prior
   gate hard-fails. Requires `issues: write` +
   `pull-requests: write` on the workflow, both added to the
   top-level permissions block.

3. Broader triggers. Dropped the `branches: [master, dev]`
   filter so every pull_request fires CI regardless of
   target — a sub-PR into `feat-cam-person` is now gated the
   same as a PR into dev. Added a `push` trigger for
   feat-* / feat/** / feature/** / fix-* / fix/** branches so
   developers get feedback before opening a PR. Concurrency
   group keyed on `head_ref || ref` with
   `cancel-in-progress: true` dedups push-vs-PR double runs
   and cancels stale runs on rapid pushes.

PR template's Testing Coverage section rewritten to the
two-table format (#### Automated tests + #### Quality gates)
so the template reflects the actual post-Vitest state, and
the checklist's "All GitHub Checks have passed" item now
explicitly references the label.

CHANGELOG.org TODO refreshed with an INFRASTRUCTURE > TESTING
follow-up tracking broader coverage (composables, widgets,
landing page computed logic) once the foundation lands.

Verified locally: `npm run lint` 0 errors (6 documented
false-positive warnings), `npm run test` 17 passing,
`npm run build` ~1.0s clean.

Modified-by: Cristian D. Moreno (Kyonax) <kyonax25@gmail.com>
@github-actions
Copy link
Copy Markdown

Protected Files Modified

The following protected files were changed in this PR:

  • CHANGELOG.org was modified

Please ensure these changes are intentional and have been reviewed carefully.

@Kyonax Kyonax merged commit 6a0931c into dev Apr 15, 2026
6 checks passed
@Kyonax Kyonax deleted the websocket-cam-person branch April 15, 2026 18:19
@Kyonax Kyonax mentioned this pull request Apr 15, 2026
13 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Release The PR is doing a Version bump Special Deployment The PR has Special Deployment steps

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant