Commit graph

6 commits

Author SHA1 Message Date
ScreenTinker 89cdd1052a CI: give the parity baselines the tags they judge against
main has been red since 4f7b4e3 for a reason visible nowhere in its diff.

The player-parity baselines describe what an UN-UPDATED display can do, so
they are judged against the SHIPPED source — `git show <latest tag>:…` —
rather than the working tree. actions/checkout defaults to a shallow clone
with no tags, so that lookup found nothing and the suite fell back to the
working tree. In a tree where today's QA had just fixed the players'
set_volume payload bug, the biconditional then demanded that
BASELINE.web/tizen gain audio.volume — for displays that cannot possibly
have the fix yet. Green locally, red in CI, and the failing assertion names
a baseline rather than the checkout that caused it.

So fetch the tags in the test job, and make the biconditionals SKIP when
there are none instead of asserting against the wrong source. That fallback
was never a slightly-early assertion; it was an inverted one. A skipped
assertion announces itself, a wrong one does not.

Verified both ways: with tags 15/15 pass, in a tagless shallow clone 13
pass + 2 skip + 0 fail (previously 1 fail).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014skWYXJUWhF73EvNPgB2AS
2026-08-06 20:54:47 -05:00
screentinker eb794cefbd
ci(android): cache Gradle to stop wrapper-download flakes (#226)
The android-test job ran ./gradlew with no Gradle caching, so every run
re-downloaded the full Gradle distribution (gradle-8.5-bin.zip, ~130MB) from
services.gradle.org. A transient "Connection reset" mid-download failed the job
before any test ran (stack bottomed out in org.gradle.wrapper.Install.forceFetch)
— e.g. run 30063222009 on the npm-audit-fix PR, which passed on a plain re-run.

Add `cache: gradle` to the existing setup-java step. It caches ~/.gradle/caches
and ~/.gradle/wrapper; the wrapper cache holds the distribution zip, so cache-hit
runs skip the download entirely — removing the network dependency that flaked.
Also speeds the job up (build cache reuse).

Residual: a cold cache (first run per key, or a gradle-wrapper.properties change)
still downloads once. If that proves flaky too, a download retry can be layered on,
but caching turns "download every run" into "download rarely".

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 22:24:21 -05:00
ScreenTinker c1b9c27f3a docs(api): OpenAPI spec, Redoc at /docs, CI spec-lint
- docs/openapi.yaml: the public, token-reachable surface only, with the auth model
  (Bearer st_) and a per-operation x-required-scope (read<write<full). JWT-only routers
  are excluded by design.
- Serve /openapi.yaml + /docs (Redoc via a vendored standalone bundle, no CDN so it
  works air-gapped; /docs is CSP-exempt). docs/ is bundled into the release tarball.
- CI: redocly lint + a public-only guard that fails loudly if a JWT-only path ever leaks
  into the spec.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 18:45:09 -05:00
ScreenTinker 2ccf3264a9 feat(scheduling): per-item schedule blocks (#74 dayparting, #75 auto-expire)
Some checks are pending
CI / Unit tests (node --test) (push) Waiting to run
CI / Android unit tests (Kotlin schedule evaluator vectors) (push) Waiting to run
CI / Boot smoke + version check (push) Waiting to run
Each playlist item can carry schedule blocks (active days, start/end
time-of-day, optional start/end dates). An item plays when the screen's
local "now" matches at least one block; an item with no blocks always
plays. #74 covers time-of-day/day-of-week windows including overnight
wrap; #75 covers inclusive date ranges (auto-expiry). Evaluation is
on-device, so dayparting and expiry work offline.

- Shared evaluator contract: shared/schedule-vectors.json (39 vectors —
  DST US+AU, overnight-wrap anchoring, timezone correctness, date
  boundaries). Canonical JS evaluator in server/lib/schedule-eval.js;
  Kotlin and Tizen ports kept in lockstep by drift guards (Tizen byte-diff
  test, Kotlin JUnit reads the shared JSON, new android-test CI job).
- All three players (web, Android, Tizen) filter by schedule against their
  own clock, idle with a "Nothing scheduled" message + 30s re-check when
  everything is filtered, and fail open on any evaluator error.
- Editor: per-item schedule modal + row badge in the playlist editor;
  client validation mirrors the server; editing marks the playlist draft.
- Part B (behaviour change): device/group schedule overrides now evaluate
  in each device's effective timezone instead of server-local time.
- Device detail shows the reported timezone + a clock-skew warning.
- i18n for en/es/fr/de/pt across all new strings (namespaced itemsched.*
  to avoid colliding with the device-schedule calendar's schedule.*).
- CHANGELOG documents the feature, the Part B change, the fail-open
  guarantee, and the scheduled-single-video re-render tradeoff.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 15:46:41 -05:00
ScreenTinker 4771f62623 ci: release pipeline (tarball, tizen wgt, multi-arch docker) + Docker packaging
- .github/workflows/release.yml: on a v* tag - verify the tag matches VERSION
  (fail-fast guard), run tests, build a source tarball + the unsigned Tizen .wgt
  and publish a GitHub Release with generated notes, and build+push a multi-arch
  (amd64 + arm64) image to ghcr.io/screentinker/screentinker:<version> + :latest.
  The Release (artifacts) and the docker push are independent jobs, so an
  arm64/QEMU docker failure does not block the GitHub Release and is re-runnable.
  Nothing deploys to prod. APK-build-in-CI left as a TODO (keystore secret).
- Dockerfile + .dockerignore: multi-stage node:20-slim image with server +
  frontend + VERSION + scripts; DATA_DIR=/data volume for db/uploads/jwt-secret.
  Verified to build, boot, serve the dashboard + web player, and persist state.
- docker-compose.example.yml: /data volume, SELF_HOSTED, a node-fetch healthcheck
  against /api/status, and an admin-lockout recovery note (reset-admin.js).
- server.js: resolve the OTA APK from DATA_DIR first (a container can mount one
  at /data/ScreenTinker.apk), fall back to the legacy in-repo path, 404 gracefully.
- ci.yml: bump checkout/setup-node to v6 (clears the Node-20 action deprecation).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:44:51 -05:00
ScreenTinker e2cd64054a ci: add CI workflow (unit tests + boot/version smoke)
- test job: node 20, npm ci + npm test in server/ (66 tests).
- smoke job: boot the server against a fresh SQLite db with SELF_HOSTED, then
  assert /api/status is ok and reports exactly the VERSION file (proves the
  single-source-of-truth wiring end to end).
- triggers: push and PR to main, plus manual workflow_dispatch. Concurrency
  cancels superseded in-flight runs per ref.
- upgrade-path job left as a TODO (needs a release tag earlier than HEAD).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:24:46 -05:00