Commit graph

2 commits

Author SHA1 Message Date
ScreenTinker 4e1de8ec0e Make the Tizen and BrightSign players do what they say they do
Both players carried calls that compile, read correctly, and are documented to
do something else. Verified line by line against docs.brightsign.biz and
Samsung's Smart TV Filesystem reference; every fix below cites the doc that
proves it, and the linter has been extended so each one fails here next time.

TIZEN

The offline media cache could never have worked on a panel. Its adapter used
the deprecated Filesystem API in three ways the IDL rules out:
`tizen.filesystem.resolve()` is declared `void`, so `var dir = resolve(...)`
was always undefined and MediaCache.create() returned null on every panel in
the fleet; `openStream()` is asynchronous, so appendPart read `written` before
any callback could run and returned 0 forever; and `moveTo()` is asynchronous,
belongs on the parent directory, and takes (origin, destination) — it was
called on a file handle with the arguments transposed. Rewritten against the
5.0 synchronous FileSystemManager, which is genuinely synchronous and is what
the decision layer needs. A Tizen 4.0 panel now reports available() false
instead of being handed a cache that silently writes nothing.

Writes are now POSITIONED rather than appended at EOF. Power cut between a
write and the index save — the exact event this feature exists for — replayed
the last chunk, and an append landed it twice: a silently corrupt video that
promoted as complete. A positioned write makes the replay idempotent.

Three decision-layer bugs alongside it: a 206 with no readable Content-Range
fell back to Content-Length, which is the CHUNK length, so the first megabyte
of a 50MB video promoted as a complete 1MB asset; a 200 whose body was short of
its own Content-Length returned 'done'; and a server with no ETag or
Last-Modified was re-fetched from zero on every sweep, forever, on precisely
the marginal link this feature exists to be gentle on.

The volume slider was dead. The dashboard sends `{level: 0..1}`; this handler
read `value`/`volume` as a 0..100 percentage, so it matched nothing and logged
"no usable value in payload" on every slider move while the panel declared
audio.volume as working. Both halves had to move together — taking `level` as a
percentage turns 50% into 0.5%, which is inaudible and looks like a fix.
Verified by driving the real handler in headless Chrome, before and after.

BRIGHTSIGN

FindMemberFunction is documented as available only when
roDeviceInfo.HasFeature("FindMemberFunction") is true. It was called
unguarded from the capability probe and from host telemetry — both on the event
loop — so a player without the feature would have died within a minute of boot
and taken the display with it. The guard needed guarding.

The boot report never arrived. The host flushed its buffer straight after
Show(), before the page had been fetched, while the player correctly waits for
its socket before subscribing. Between two correct decisions every boot line
fell on the floor. The host now waits for the page's `probe`, and the bridge
buffers until a consumer registers.

offline.cache was claimed on `navigator.serviceWorker` being present. It is
present on a BrightSign widget and will not run a worker — our XT245 passes the
check and never fetches sw.js. Now requires a controller, matching the web
player. Removed from the brightsign baseline for the same reason.

display.resolution was claimed on @brightsign/videooutput, which has no
setMode at all; mode setting lives on @brightsign/videomodeconfiguration.

roStorageHotplug.GetStorages() answers "USB1:/" while GetStorageStatus() is
documented as unreliable for "USBn:" — feeding one to the other re-created the
bug the static fallback list exists to avoid, and only on the OS versions that
have the enumerator.

dual/clone output mode put two full-screen widgets on output ONE, on top of
each other, while output two stayed dark: roHtmlWidget has no output selector,
and a second output is addressed by its display_x/display_y within the
SetScreenModes canvas. Now positioned properly, or refused with a reason.

Also: a manifest missing sha256/size passed `invalid` into typed parameters, a
runtime error at the call the comment already described and did not prevent;
storage_quota was a string where the docs say use a double; and the comment
crediting brightsign_js_objects_enabled with gating require("@brightsign/*")
named the wrong flag — it is nodejs_enabled.

TESTS

The two suites that mattered most were the ones that passed while the code was
broken, because they asserted on source text or against a fake more correct
than the platform. The host-diagnostics regexes now execute the bridge; the
media-cache suite now drives the shipped adapter against a fake tizen.filesystem
written from Samsung's IDL. Ten new rules in the BrightScript linter, each
verified to fail against the source it was written to reject.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL
2026-08-06 10:51:15 -05:00
ScreenTinker db8846a139 BrightSign: report what the host knows, through the channels the other players use
Some checks are pending
CI / Unit tests (node --test) (push) Waiting to run
CI / OpenAPI spec lint (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
A BrightSign could see things the page cannot ask for — the uptime, the wired IP,
the video mode in force, which volume it booted from, whether a staged package
applied — and it printed all of it to a serial console. On a panel on a wall that
is the same as reporting nothing.

The cost was concrete and recent. A single bad string literal stopped the host
script compiling; the only evidence anywhere was one line on a cable, and from
the server the display looked identical to one that had never started. Diagnosing
it needed someone physically present with a serial adapter. Every other player
reports its own failures.

Three hops, each thin: the host posts, the bridge carries, the player emits on
the channels it already uses (device:log, device:event, and the telemetry the
heartbeat has carried for releases).

The pre-widget phase is the part that matters and the part that was hardest to
reach — the storage probe, a pending package being applied, the video mode being
set, all happen before there is a page to talk to. Those lines accumulate in a
buffer and flush the moment the widget exists, so the boot story arrives even
though it happened before anyone could listen. BrightScript has no global store
here (no GetGlobalAA), so the buffer is threaded explicitly; losing the boot
entirely was the worse option.

Two things become incidents rather than console lines: the watchdog rebuilding a
wedged widget, which is the most important thing a player does unattended and
previously healed in silence — a panel rebuilding itself every two minutes looked
exactly like a healthy one — and a load-error, which now names the resource that
failed. Both use event types the server actually accepts; an invented one is
dropped silently and would have been just as invisible.

Host telemetry merges into the existing snapshot rather than opening a channel,
and the host's numbers win where they overlap: navigator.storage.estimate()
describes the widget's cache quota, not the disk, so a panel can report gigabytes
free while the volume holding them is full.

Two API traps caught in my own new code before it shipped, both the same shape as
the ones being fixed: Str() applied to a value already documented as a String
(it is for numbers, and would abort the event loop while reporting a diagnostic),
and Stri() handed a float from an inline division. The checker now pins the first.

Verified on the XT245: boots clean, plays, online. The bridge and player halves
are served BY the server, so they take effect on the next deploy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL
2026-08-06 00:02:33 -05:00