mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 22:03:13 -06:00
Merge docs/readme-catchup: README catch-up and CHANGELOG backfill
# Conflicts: # scripts/bump-version.sh
This commit is contained in:
commit
dd7596a674
347
CHANGELOG.md
347
CHANGELOG.md
|
|
@ -1,5 +1,352 @@
|
|||
# Changelog
|
||||
|
||||
## 1.9.25
|
||||
|
||||
**Android playback and account-admin fixes.** Closes #234 — a playlist that only ever showed its
|
||||
first item — plus the registration loop feeding it, and three issues found by the same reporter in
|
||||
an afternoon of testing.
|
||||
|
||||
### Fixed — Android playback (#234)
|
||||
- **A playlist no longer restarts at item one every time the Activity is rebuilt.** `PlaylistController`
|
||||
is owned by `MainActivity`, so each rebuild handed it a fresh, empty instance; the playlist then
|
||||
arrived, looked like a first load, and playback began from index 0. On a device rebuilding at every
|
||||
item boundary the second item held the screen for ~135ms — invisible, which is why it read as "only
|
||||
one item plays" rather than "it glitches". Playback position now lives outside the object being
|
||||
rebuilt and resumes if the save is recent (cold starts, stale saves and shrunk playlists all still
|
||||
begin at item one).
|
||||
- **A player no longer re-registers itself once per playlist item.** Every advance asked for a
|
||||
playlist refresh, and a refresh emits a full `device:register` — so a 10-second image re-registered
|
||||
six times a minute, per device, forever, each one running the whole identity path and pushing a
|
||||
playlist back down. The heartbeat already refreshes every 60s, so the per-item call was duplicating
|
||||
a pull that happens anyway. Measured on the reproduction: 9 registrations for 9 plays → 3.
|
||||
- **A leaked callback no longer relaunches the app in a loop.** `ProvisioningActivity` left its
|
||||
service callbacks attached after pairing, so later events re-entered a finished activity and
|
||||
restarted it — a white flash on every cycle, since Android 12+ draws a splash screen on each
|
||||
relaunch. Measured: 240 activity starts in 180s → 0.
|
||||
|
||||
### Fixed — account administration
|
||||
- **Unblock now sticks.** Per-device settings are keyed to the hardware and deliberately restore
|
||||
`blocked` across a delete + re-pair, so a block cannot be shrugged off by deleting the display.
|
||||
Unblock only ever cleared the live copy, so the saved copy put the block straight back on the next
|
||||
re-pair and there was no way out from the dashboard. Unblock now clears both; blocking still
|
||||
survives a re-pair, which is the property that made this worth getting right.
|
||||
- **A refused device says why.** A blocked panel sat on "Connecting to server" with nothing surfacing
|
||||
the server's rejection.
|
||||
|
||||
### Added
|
||||
- **Every plan is visible to platform admins,** with how many accounts, organizations and displays
|
||||
are on each, plus a flag for accounts pointing at a plan that no longer exists. A plan hidden from
|
||||
the public pricing page was previously invisible to the operator too.
|
||||
- **Player permissions can be reviewed and revoked from the setup screen.** Each row stays visible
|
||||
once granted and becomes *Manage*, instead of disappearing and leaving no way back.
|
||||
|
||||
## 1.9.24
|
||||
|
||||
**OTA control for managed panels.** Everything here is about not stranding a display: an operator
|
||||
override, a retry budget that reflects what a retry actually costs, and a stand-down that only fires
|
||||
when it should.
|
||||
|
||||
### Fixed — OTA
|
||||
- **Self-OTA now stands down only for a genuine foreign device owner.** The check was broad enough
|
||||
that a stock Android panel with no MDM at all logged "self-OTA stands down" and stopped updating.
|
||||
- **`OTA_ALLOW_MANAGED_DEVICES`** lets an operator override the stand-down when they run an MDM that
|
||||
does not distribute the player. Off by default. See the README before enabling — it does not grant
|
||||
the ability to install silently.
|
||||
- **The install retry budget went from 3 attempts to 40, and flagging moved to 3.** Telling an
|
||||
operator a panel needs a human and giving up on that panel are separate decisions, and they were
|
||||
wired to the same number. A retry is nearly free — the APK is downloaded and signature-checked once
|
||||
and reused from cache, so later attempts pull no bytes. Past the budget it settles to about one
|
||||
attempt a day, indefinitely; a new version clears the count.
|
||||
- **"Force update" is now actually forceful, and reports back.** It ignores the back-off, the attempt
|
||||
count and the MDM stand-down, and says what happened — including "already up to date", which used
|
||||
to return in silence and made a working button look broken.
|
||||
|
||||
### Fixed — playback
|
||||
- **A wipe hands the frame back cleanly at the end,** instead of briefly revealing the outgoing image
|
||||
through the transition surface.
|
||||
- **Turning off follower mode re-arms self-advance,** so a display taken out of a synchronized group
|
||||
no longer freezes on whatever was on screen.
|
||||
|
||||
## 1.9.23
|
||||
|
||||
**Scheduling on a touchscreen, and internationalization.** The weekly calendar becomes directly
|
||||
manipulable, and a large batch of user-facing strings that were never translated go through `t()`.
|
||||
|
||||
### Added
|
||||
- **The week calendar is directly manipulable** — drag and resize blocks, with grab targets big
|
||||
enough for a finger, gestures that work on a touchscreen, pointer handlers bound once rather than
|
||||
per render, and a single-day view for when a week will not fit.
|
||||
- **Schedules that run past midnight draw correctly.**
|
||||
- **The calendar opens on the working day** and explains what it is for.
|
||||
- **Empty states tell you what to do next,** based on what the account actually contains.
|
||||
- **`MAX_FILE_SIZE` is parsed properly** (bytes or a `2GB` / `1500MB` suffix), and the README
|
||||
documents the reverse proxy and CDN limits that cap an upload independently — raising the app limit
|
||||
alone often changes nothing (#233).
|
||||
- **An opt-in browser smoke test,** deliberately kept out of `npm test`.
|
||||
|
||||
### Fixed
|
||||
- **Untranslated keys are no longer shipped as user-facing text.**
|
||||
- **Teams says it is switched off** rather than showing an empty list.
|
||||
- **Proof-of-play attributes a widget play to the widget that played.**
|
||||
- **Members is in the nav,** titles reveal on touch, and a stale heartbeat no longer kills a live
|
||||
socket.
|
||||
- **A player re-establishes a socket the server closed.**
|
||||
|
||||
## 1.9.22
|
||||
|
||||
**Player identity.** Two panels running the same build could collapse into one dashboard row.
|
||||
|
||||
### Fixed
|
||||
- **Each player install gets its own identity.** The web player's fingerprint was derived from
|
||||
hardware characteristics alone, so two identical panels produced the same value and merged into a
|
||||
single device row. Identity is now per install.
|
||||
- **A screen-only panel can clear its identity from the URL,** giving a way to split a panel that had
|
||||
already merged.
|
||||
- **Crash reports record where a player crashed,** not only what it said.
|
||||
|
||||
## 1.9.21
|
||||
|
||||
**Measurement fixes.** Small, all about not lying in the numbers.
|
||||
|
||||
### Fixed
|
||||
- Event-loop lag reports zero for a window with no samples, instead of a stale figure.
|
||||
- Auth rate-limit rejections are recorded, so they can be measured rather than inferred.
|
||||
- A device fingerprint is only stored against a device that still exists.
|
||||
|
||||
## 1.9.20
|
||||
|
||||
**Scheduling across a fleet, and alerting that does not repeat itself.**
|
||||
|
||||
### Added
|
||||
- **Every screen's schedule on one calendar,** rather than one screen at a time.
|
||||
- **A schedule is stored in the timezone its screen runs in,** so a fleet spanning timezones behaves
|
||||
the way an operator means it to.
|
||||
- **An unpaired player can be recovered without a keyboard** — relevant on signage hardware with a
|
||||
remote and no text input.
|
||||
- **A BrightSign capability probe.**
|
||||
- Italian translation updated (#232).
|
||||
|
||||
### Fixed
|
||||
- **One alert per outage** instead of one per dedup window.
|
||||
- Kiosk style values are validated as CSS rather than as HTML.
|
||||
- A device's OTA rate state is cleared once it proves its identity.
|
||||
|
||||
## 1.9.19
|
||||
|
||||
### Fixed
|
||||
- Proof-of-play resolves content references rather than trusting a reported id.
|
||||
- `sharp` updated to 0.35.x, and the corrupt PNG fixture that update exposed was repaired.
|
||||
|
||||
## 1.9.18
|
||||
|
||||
### Fixed
|
||||
- **Device serialization is scoped to what each endpoint actually needs,** rather than returning a
|
||||
whole device row everywhere.
|
||||
- **A solo widget stays mounted** and sizes its keyboard to the viewport.
|
||||
- Weather-radar example: the map stays centred and bounded, and counts only the warnings on screen.
|
||||
|
||||
## 1.9.17
|
||||
|
||||
### Added
|
||||
- **Self-service password reset.**
|
||||
|
||||
### Fixed
|
||||
- **A pairing code expires on device liveness, not row age,** so a slow setup no longer runs out of
|
||||
time while the panel is sitting on the code.
|
||||
|
||||
## 1.9.16
|
||||
|
||||
**Hardening pass.** Findings from an internal auth/authorization review, described here in the same
|
||||
neutral terms as the commits: this is a public repository and detail that only helps an attacker adds
|
||||
nothing for an operator deciding whether to upgrade. Upgrade.
|
||||
|
||||
### Security / hardening
|
||||
- Break-glass admin recovery is backed by a revocable, auditable grant.
|
||||
- Access-gating six-digit codes are generated with a CSPRNG.
|
||||
- The screenshot route is authorized against the device's workspace.
|
||||
- Password login is bounded per account, not only per IP.
|
||||
- The unauthenticated telemetry store is bounded and no longer writes rows.
|
||||
- `CF-Connecting-IP` is trusted only from a Cloudflare peer, not from any trusted proxy.
|
||||
- An upload's stored type is derived from file content, and uploads are never served as documents.
|
||||
|
||||
### Fixed
|
||||
- The release tarball keeps `.env.example`, and CI asserts it is there.
|
||||
|
||||
## 1.9.15
|
||||
|
||||
### Fixed
|
||||
- **Webpage widgets carry an honest note:** sites that refuse embedding do not work on a device, and
|
||||
no client-side signal can reliably tell "blocked" from "loading" (#230).
|
||||
- The "Reload now" update toast is actually clickable (#229).
|
||||
|
||||
### Changed
|
||||
- Session token resolution centralised across the manual verify sites; the unused `optionalAuth`
|
||||
middleware dropped.
|
||||
|
||||
## 1.9.14
|
||||
|
||||
### Fixed
|
||||
- **Trial expiry auto-downgrade actually fires** (#228).
|
||||
- 11 of 13 npm advisories resolved (lockfile only) (#225).
|
||||
|
||||
### Added
|
||||
- Stripe checkout accepts promotion codes (#227).
|
||||
|
||||
## 1.9.13
|
||||
|
||||
**Content library.** A batch of workflow features for libraries bigger than a handful of files.
|
||||
|
||||
### Added
|
||||
- Multi-file upload (#222), multi-select with batch delete and batch move (#224).
|
||||
- Server-side search, type filter and sort (#221).
|
||||
- Subtitle / caption support as a content property (#223).
|
||||
- **Unstable-connection mode** — caps YouTube at 720p for weak Wi-Fi (#220).
|
||||
- The Add Display modal shows the server URL, and `/download/apk` links GitHub Releases (#210).
|
||||
|
||||
### Fixed
|
||||
- YouTube ENDED safety net for Shorts and flaky Android TV (#219).
|
||||
- Visible D-pad focus stroke on the Android setup buttons (#218).
|
||||
- Uploads respect the current folder (#211).
|
||||
|
||||
## 1.9.12
|
||||
|
||||
### Added
|
||||
- **TOTP two-factor authentication** and **email verification on signup**.
|
||||
- **Proof-of-play on Android and Tizen,** closing the Tizen parity gaps.
|
||||
- Tizen SSSP install.
|
||||
- Designer-made widgets can be edited in the designer again, including reconstruction of legacy ones
|
||||
(#207).
|
||||
|
||||
### Fixed
|
||||
- Web player cold-start crash from a hoisting error in `renderSeq`.
|
||||
- The advance timer is reconciled on group/wall mode transitions (#200, #208).
|
||||
- Weather elements can switch to metric (#206).
|
||||
|
||||
## 1.9.11
|
||||
|
||||
### Added
|
||||
- **Transition engine** — GL wipes across the web, Tizen and Android players, including image↔video
|
||||
transitions (#204).
|
||||
|
||||
### Fixed
|
||||
- Android supersede wedge and leak, plus a stale-video guard on web and Tizen (#205).
|
||||
|
||||
## 1.9.10
|
||||
|
||||
**Directory board and widget stability.**
|
||||
|
||||
### Added
|
||||
- Directory board: panel-ring scroll, in-place refresh, per-device frame diagnostic (#203), and
|
||||
JSON/CSV import with logo-replaces-title (#195).
|
||||
|
||||
### Fixed
|
||||
- **A zero-duration widget no longer self-loops.** It pegged the Android main thread (#198), and the
|
||||
server now floors `duration_sec` so no player can be handed the condition (#199).
|
||||
- Buffered widget swap and schedule-aware solo-board hold, killing the directory-board black flicker
|
||||
(#202); decode-gated image double-buffer does the same for Tizen stills (#193, #187).
|
||||
- Directory board scroll stutter from a seamless-loop gap mismatch (#197).
|
||||
- The cross-origin header is set on the route that actually serves content (#196).
|
||||
- Modals scroll instead of overflowing the viewport (#194).
|
||||
|
||||
## 1.9.9
|
||||
|
||||
### Fixed
|
||||
- **Pairing:** closed a deferred-offline reclaim race and made same-code adopt idempotent (#192).
|
||||
|
||||
## 1.9.8
|
||||
|
||||
### Added
|
||||
- **Directory-search widget** — interactive search of a directory board, live-synced (#188).
|
||||
- Dashboard version loading indicator with an immediate first poll (#181).
|
||||
|
||||
### Fixed
|
||||
- YouTube Shorts render 9:16 instead of in a landscape frame (#184, #189).
|
||||
- A stuck download back-off resets on content change and network reconnect (#170, #190).
|
||||
- The soft keyboard appears for PIN/URL dialogs over immersive fullscreen (#191).
|
||||
- Thumbnail images use `data-auth-src` in modals and views (#182), with hydration lazy by default
|
||||
(#185).
|
||||
- Raspberry Pi setup handles both `chromium-browser` and `chromium` package names (#183).
|
||||
|
||||
## 1.9.7
|
||||
|
||||
### Added
|
||||
- **SMTP transport** as an alternative to Microsoft Graph for email (#173, #179).
|
||||
|
||||
### Fixed
|
||||
- **A reinstalled panel reclaims its device row** instead of being blocked (#180).
|
||||
|
||||
## 1.9.6
|
||||
|
||||
### Added
|
||||
- **Device incident log** — offline cause, network-vs-reboot, display-sleep (#175).
|
||||
- IndexNow and landing-page optimization (#177); integrations internal linking (#178).
|
||||
|
||||
### Fixed
|
||||
- **Tizen portrait and flipped video via AVPlay hardware-plane rotation** — CSS rotation cannot touch
|
||||
the hardware video plane and produced a black screen (#170, #174).
|
||||
- `/integrations/` is served explicitly so the nav link is not the login page.
|
||||
- CI uses OS-assigned ephemeral ports for subprocess suites, ending a port-collision flake (#176).
|
||||
|
||||
## 1.9.5
|
||||
|
||||
**Group sync, device-owner foundation, and agency folders.** The largest release in the 1.9.x line.
|
||||
|
||||
### Added
|
||||
- **Per-group synchronized playback** — every member of a group derives the same (index, position)
|
||||
from a server-disciplined clock and a deterministic schedule, so displays start and end each item
|
||||
together. Offline-native (no server needed at play time) and split-brain-proof (no leader role).
|
||||
Includes snap-on-load, a warm next-clip double buffer, and in-place duration edits (#167).
|
||||
- **Device-owner tier foundation** — QR provisioning, content expiry, and the tier substrate the
|
||||
Tier-2 controls build on (#168).
|
||||
- **Tier 0/1 system controls with no device-owner dependency** — volume, brightness and screen
|
||||
timeout on ordinary panels (#160, #169).
|
||||
- **Per-token upload folder for agency tokens** — auto-created and subtree-confined (#158, #171).
|
||||
- **OTA self-update kill switch** — global, per-device, and MDM auto-detect (#166).
|
||||
- Dashboard version indicator with a GHCR update check (#165).
|
||||
|
||||
### Fixed
|
||||
- **Rotation-aware media** — a portrait photo is upright on both the dashboard and the player
|
||||
(#170, #172).
|
||||
- `bump-version.sh` handles the env-overridable Android version (#168).
|
||||
|
||||
## 1.9.4
|
||||
|
||||
### Added
|
||||
- **Hidden settings menu on Android,** opened by a multi-tap BACK/ESC sequence and gated by a PIN;
|
||||
the PIN is server-provisioned per device and surfaced on the dashboard, replacing a hardcoded
|
||||
`0000` (#152).
|
||||
|
||||
### Fixed
|
||||
- **A player sends its device id and token on reconnect before pairing** (#164).
|
||||
- Android provisioning and playback robustness.
|
||||
- Playlist `GET /:id` returns item schedules, so the editor shows them (#156).
|
||||
- Draft preview runs in a server-side session to bypass CSP (#151).
|
||||
- Tizen player wedge on a shared `#stage` (same class as #162).
|
||||
|
||||
## 1.9.3
|
||||
|
||||
**Liveness contract v4 and per-device settings that survive a re-pair.** Follows 1.9.2-patch3.
|
||||
|
||||
### Added
|
||||
- **Exit-signal contract v1** — a player tells the server it is going away, across the server, the
|
||||
APK, the `.wgt` and the browser player, so "offline" can distinguish a clean exit from a
|
||||
disappearance. Surfaced in the dashboard as an offline annotation with a tooltip, a filter drill-in
|
||||
and a list label.
|
||||
- **Liveness contract v4** — uniform heartbeat acknowledgement, ack-gap tracking, a throttle-aware
|
||||
client watchdog, browser lifecycle triggers, and an identity block, implemented across the server,
|
||||
the APK, the `.wgt` and the browser player. Includes a three-state dashboard liveness badge.
|
||||
- **Per-device settings survive delete + re-pair,** keyed to the hardware fingerprint: a re-paired
|
||||
panel comes back with its name, orientation, timezone, notes and playlist already set (#150).
|
||||
|
||||
### Fixed
|
||||
- **Legacy `-patchN` builds are treated as released versions,** so the existing fleet is offered
|
||||
updates.
|
||||
- Tizen: watchdog config-proofing, teardown hygiene, dead-screen self-heal, offline snapshot,
|
||||
keep-awake re-assert and a suspend/resume handler.
|
||||
- Dashboard: `device-detail.js` parse and runtime errors that took out the whole view; liveness badge
|
||||
filter regression; list-view legibility.
|
||||
- CSP allows the Cloudflare Web Analytics beacon to load *and* report.
|
||||
|
||||
## 1.9.2-patch2
|
||||
|
||||
**Server/CMS-only field-safe net for #148 — NO Android APK, players unchanged.** Makes the
|
||||
|
|
|
|||
90
README.md
90
README.md
|
|
@ -32,7 +32,7 @@ ScreenTinker is a free, open-source **digital signage CMS** you can self-host on
|
|||
- **Widgets** — clocks, weather, RSS tickers, text/HTML, webpages, social feeds, and Directory Board (scrolling lobby tenant/room/staff directories with dark/light themes, category management, and anti-burn-in motion)
|
||||
- **Kiosk mode** — interactive touchscreen interfaces
|
||||
- **Proof-of-play** — per-content and per-device analytics, hourly/daily breakdowns, CSV export for ad verification
|
||||
- **Device telemetry** — battery, storage, RAM, CPU, WiFi signal strength, and uptime reported by Android players
|
||||
- **Device telemetry** — battery, storage, RAM, CPU, Wi-Fi signal strength and uptime reported by the players, plus both of a display's addresses: its **local (LAN) IP** as the player sees itself, and the public/WAN address the server saw it connect from. Wi-Fi network name is included where the platform allows it (Android 10+ needs an opt-in location permission — see Device Setup)
|
||||
- **Offline resilience** — both web and Android players keep displaying cached content during server or internet outages (Android ContentCache, web player Service Worker); state syncs when connectivity returns
|
||||
- **Mobile-responsive** — full management dashboard and landing page work on phones and tablets
|
||||
- **Workspaces** — multi-tenant data model: organizations contain workspaces, workspaces contain devices/content/playlists/schedules; users can be members of multiple workspaces and switch via a dropdown in the sidebar
|
||||
|
|
@ -46,6 +46,7 @@ ScreenTinker is a free, open-source **digital signage CMS** you can self-host on
|
|||
- **Security** — JWT auth, bcrypt hashing, parameterized SQL, rate-limited endpoints, per-user ownership checks on all resources, ongoing auth/IDOR/XSS audits
|
||||
- **Built-in billing** — Stripe integration for SaaS subscriptions (optional)
|
||||
- **Auto-update** — OTA updates pushed to devices automatically
|
||||
- **Public REST API** — scoped personal access tokens (`read` / `write` / `full`) over the same resources the dashboard uses, workspace-confined by construction. Documented as an OpenAPI 3.1 contract ([`docs/openapi.yaml`](docs/openapi.yaml)) and browsable on any instance at `/docs` (served locally, no CDN, so it works air-gapped)
|
||||
- **Activity log** — full audit trail of user and system actions
|
||||
|
||||
## Architecture
|
||||
|
|
@ -92,7 +93,19 @@ Schema migrations run automatically the first time the server starts after a git
|
|||
|
||||
## Supported Platforms
|
||||
|
||||
Android TV, Fire TV, Raspberry Pi, Windows, ChromeOS, LG webOS, Samsung Tizen, and any device with a web browser.
|
||||
Android TV, Fire TV, Raspberry Pi, Windows, ChromeOS, LG webOS, Samsung Tizen, BrightSign, and any
|
||||
device with a web browser.
|
||||
|
||||
Anything with a reasonably modern browser can be a display without installing anything: point it at
|
||||
`/player`. The native players add what a browser cannot: the **Android APK** gives you unattended boot,
|
||||
OTA self-update, remote power and touch injection, and a content cache that survives a reboot; the
|
||||
**Tizen `.wgt`** gives you an installed app that launches itself on the TV. Tizen does not
|
||||
self-update — new versions are installed the same way the first one was.
|
||||
|
||||
> **BrightSign** runs the unmodified browser player (verified on Series 5 / Chromium 120) and needs
|
||||
> no separate build. One caveat worth knowing before you rely on it: BrightSign's HTML widget does
|
||||
> not always survive the page reload the player performs when you deploy new content, and may need a
|
||||
> restart to come back. Treat it as working but less hands-off than the native players.
|
||||
|
||||
## Self-Hosting
|
||||
|
||||
|
|
@ -161,6 +174,52 @@ Two things to know before enabling it:
|
|||
- **It is read by the player, not the server**, so only players new enough to understand
|
||||
`allow_managed` honour it. Older players keep standing down regardless.
|
||||
|
||||
#### When a display will not update itself
|
||||
|
||||
OTA is per-display and can be turned off per display. If one is not taking an update, the order to
|
||||
check is:
|
||||
|
||||
1. **Is OTA enabled for it?** There is a per-display toggle; a display with it off will never
|
||||
self-update, by design.
|
||||
2. **Is it standing down for an MDM?** It reports `manual_update_required` if so — see above.
|
||||
3. **Has it been retrying and failing?** Retrying and telling you about it are two separate
|
||||
things, on purpose:
|
||||
|
||||
- After **3** failed installs the display **flags itself as needing attention** in the dashboard.
|
||||
A human is demonstrably required by then, so it says so early rather than at the end.
|
||||
- It **keeps retrying anyway**, up to 40 attempts. Attempts after the first are close to free —
|
||||
the APK is downloaded and signature-checked once and then reused from cache, so retry number
|
||||
twelve pulls no bytes.
|
||||
- Past that it settles to about **one attempt a day**, indefinitely. It never gives up for good,
|
||||
and a new version clears the count — so a display stuck for a week still picks up the next
|
||||
release on its own.
|
||||
|
||||
The flag is what to watch for. Silence is not the signal.
|
||||
|
||||
**Force update** — per display, or as a group command — deliberately ignores the back-off, the
|
||||
attempt count *and* the MDM stand-down, and tries straight away. It reports back either way,
|
||||
including "already up to date", so the button never just appears to do nothing. What it cannot do is
|
||||
invent permissions: if installs need a confirmation tap on that hardware, forcing still raises the
|
||||
dialog. It is the right button once you have fixed whatever was breaking the update.
|
||||
|
||||
#### Deleting and re-pairing a display
|
||||
|
||||
A display's settings are keyed to the hardware, not to its row in the database. Delete a display and
|
||||
pair the same panel again and it comes back with its previous **name, orientation, timezone, notes
|
||||
and assigned playlist** already set — you do not have to configure it twice, and a panel that is
|
||||
physically hard to reach does not need a visit. (The playlist only returns if it still exists; a
|
||||
deleted one is not resurrected.)
|
||||
|
||||
Two consequences that are easy to misread:
|
||||
|
||||
- The old playlist reappearing is ScreenTinker restoring it, not a bug. If you deleted the display
|
||||
in order to *clear* it, change the playlist after re-pairing rather than before.
|
||||
- **A blocked display stays blocked**, deliberately. Blocking is a security control, so it must not
|
||||
be defeatable by deleting the display and pairing again. Use **Unblock** — that clears the stored
|
||||
block as well as the live one. (Before 1.9.25, Unblock only cleared the live one and the block came
|
||||
back on the next re-pair; if you have a display that refuses to pair for no visible reason, unblock
|
||||
it once on this version.)
|
||||
|
||||
#### Raising the upload limit
|
||||
|
||||
`MAX_FILE_SIZE` sets what **the application** accepts. It is usually not the only limit, and it
|
||||
|
|
@ -218,6 +277,19 @@ If you want to charge your users, plug in your own Stripe keys. Without them, al
|
|||
|
||||
The default plans are: Free (2 devices), Starter (8 devices), Pro (25 devices), and Enterprise (unlimited). Edit the `plans` table to change pricing, limits, or add/remove tiers. In self-hosted mode, the first user gets Enterprise automatically.
|
||||
|
||||
#### Plans and comped accounts
|
||||
|
||||
Platform admins get a plan overview under **Admin → Subscription Plans**: every plan on the instance with how
|
||||
many accounts, organizations and displays are on each, so you can see what people actually use
|
||||
before changing a price or retiring a tier. It also flags accounts pointing at a plan that no longer
|
||||
exists, which otherwise surfaces only as odd entitlement behaviour.
|
||||
|
||||
A plan marked **inactive** disappears from the customer-facing pricing page but keeps working
|
||||
normally for anyone already on it. That is how you run a comped, beta or legacy tier without
|
||||
advertising it — put the account on the hidden plan and it simply gets those limits. The overview
|
||||
above deliberately lists hidden plans too (marked as such), because the previous behaviour was that
|
||||
a hidden plan was invisible to the operator as well as the customer.
|
||||
|
||||
#### Google OAuth
|
||||
|
||||
Let users sign in with Google.
|
||||
|
|
@ -528,6 +600,20 @@ keytool -genkey -v -keystore android/release-key.jks -keyalg RSA -keysize 2048 -
|
|||
- **Any browser**: Open `https://your-instance/player` in kiosk/fullscreen mode
|
||||
4. Enter the pairing code shown on the device
|
||||
|
||||
On the Android player, the setup screen lists the permissions it wants and lets you revisit any of
|
||||
them later — each row stays visible once granted and turns into **Manage**, so you can check or
|
||||
revoke what you gave it rather than having the option disappear.
|
||||
|
||||
One of those rows is **optional and off by default**: granting location lets the player report the
|
||||
**Wi-Fi network name** for the display. Android 10 and later will not reveal the SSID without it.
|
||||
Nothing else changes if you skip it — the display works identically and still reports signal
|
||||
strength, and the dashboard says the network name needs that permission rather than showing a blank.
|
||||
|
||||
> **One playlist per display, and how to run more.** A display has a single playlist at a time.
|
||||
> To rotate between several, use **Scheduling** — "Playlist A 9am-5pm, Playlist B evenings", or
|
||||
> different playlists on different days — and the display switches on its own, offline included,
|
||||
> once the schedule has reached it.
|
||||
|
||||
> **Troubleshooting a player** (stuck on "Connecting to server", re-pointing a
|
||||
> device to a different server, or connecting adb over Wi-Fi): see
|
||||
> [docs/android-troubleshooting.md](docs/android-troubleshooting.md).
|
||||
|
|
|
|||
|
|
@ -89,7 +89,18 @@ sed -i -E "/^<\?xml/! s/([[:space:]]version=\")[0-9][^\"]*(\")/\1${NUMERIC}\2/"
|
|||
# published API identity, not a build label.
|
||||
sed -i -E "0,/^ version:/s/^( version:[[:space:]]*).*/\1${NUMERIC}/" docs/openapi.yaml
|
||||
|
||||
# 6) commit + annotated tag (no push)
|
||||
# 6) CHANGELOG guard. Deliberately NOT auto-generated — a generated changelog reads like
|
||||
# documentation while saying nothing, and the entry has to come from whoever knows what
|
||||
# shipped. This only refuses to let a release be cut silently without one, which is how the
|
||||
# file fell 23 versions behind.
|
||||
if ! grep -q "^## ${NEW}$" CHANGELOG.md 2>/dev/null; then
|
||||
echo
|
||||
echo " WARNING: CHANGELOG.md has no '## $NEW' entry."
|
||||
echo " Add one before pushing the tag — the release notes are read from it."
|
||||
echo
|
||||
fi
|
||||
|
||||
# 7) commit + annotated tag (no push)
|
||||
git add VERSION server/package.json server/package-lock.json android/app/build.gradle.kts tizen/config.xml docs/openapi.yaml
|
||||
git commit -q -m "chore(release): v$NEW"
|
||||
git tag -a "v$NEW" -m "ScreenTinker v$NEW"
|
||||
|
|
|
|||
Loading…
Reference in a new issue