Replies: 10 comments
|
Before the contract, one thing about the current state that changes the sequencing rather than the design. Lineups is a unified surface over a model that is only partly unified. That is my problem to fix, not yours, and it argues for splitting your first slice in two: read plus control needs none of it and can land now; authoring lands on top of a single write path once I have one. Answers in your order:
The contract-only PR is welcome. Two things I would like settled in it: the read shape has to carry enough of the underlying model that an advanced record is still fully describable when |
|
Read + control is live as of v0.287.0, so the first half of your slice has real endpoints to target rather than a proposal: Details that affect your client:
Authoring is unchanged from what I said earlier: it waits on #204, the single write path. Creation still fans out to three stores, so Two things I'd still like settled in your contract PR: whether the read shape above carries enough to describe an advanced Lineup completely when it isn't natively editable, and the job-kind question. The paths aren't in the vendored spec on my side, deliberately, so there's nothing of mine for you to work around. |
|
Thanks — I have aligned the Companion 0.8 read/control contract and Swift client with the live v0.289.2 behavior. I have settled the two remaining contract questions as follows. 1. Read shapeKeep the implemented projection unchanged:
Add the fields needed to describe an advanced Lineup completely:
The internal 2. Job kindPlease use The action contract otherwise follows the live implementation:
Pairing uses I have kept this contract/client-only: no native authoring endpoints or Lineups UI are included yet. |
|
Both settled as you asked, live on edge in v0.291.0. Read shape. The projection carries the full set now: Two details worth knowing:
Job kind. Your absent-means-unknown reader is the right call and it's what makes the rest safe. Nothing in the action contract, the scope pairing, or the response codes moved from what you tested against in v0.289.2. On re-pairing for pre-existing tokens: agreed, no migration path from here either. The per-token grant work in #207 is scoped to |
|
Thanks — I checked the v0.291.0 projection against the Companion and the full read shape plus I found one small I’ll add a narrowly scoped Companion fallback for already-deployed v0.287–v0.291 responses that only upgrades an exact |
|
Good catch, and it's worse than a wrong path: Fixed in v0.291.1. Your narrow fallback for v0.287–v0.291 is the right call, please keep it. Those releases are out and I'd rather your users get a working button than a correct complaint. Nothing else moved: the read shape and |
|
Authoring is in, v0.292.0. One change needs your attention before 0.8 ships, so I've put it first. A missing permission now answers 403, not 401I'd parked this earlier as needing coordination. It stopped being optional once
If your client branches on 401 to trigger re-pairing, that branch will now fire only when it should. This applies to every scope, not just the Lineup ones. AuthoringCreate takes the four intents and goes through the same builder the web wizard uses, so a Lineup authored from the app and one authored from the web are the same record made the same way. Body: Three refusals, all deliberate:
Fields a PATCH doesn't mention keep their stored values, and a reordered dashboard keeps its links, conditions and refresh override. Errata on this sideThree additions to the vendored spec copy, all marked pending your re-vendor: error codes
|
|
One read/control edge case surfaced while testing Daily Lineups against the current contract. Daily and interval Lineups created through the schedule path intentionally project device_ids as an empty array. The engine still fires them schedule-style to the dashboard own bound displays, and Companion can place such a Lineup under the correct Display using that dashboard binding. However, POST /api/app/v1/lineups/{id}/actions currently defaults targets from deck.device_ids and also filters an explicit device_ids subset against deck.device_ids. With the empty array above, play always returns invalid_target: “The lineup has no bound displays.” In Companion this leaves the Daily play control with no valid target; merely enabling the button client-side would only expose the server error. Which server-owned representation would you prefer here?
The second option seems closest to the current Daily/Interval semantics, but I do not want Companion to infer action applicability or target authorization itself. Once target resolution is server-owned, Companion can use the returned effective targets consistently while preserving device_ids as the explicit Lineup binding. I will also add a fixture covering a Daily Lineup with device_ids: [] once we settle the intended behavior. |
|
Thanks — we are starting the native create/edit implementation against the v0.292.0 authoring contract. I have two narrow follow-ups; neither needs to block the client work. 1. Read the current permission grant
Would you be comfortable adding an authenticated session read, for example: with a small response such as: {
"token_id": "...",
"scopes": ["devices:read", "lineups:read", "lineups:control", "lineups:write"]
}The app will treat this as progressive enhancement: while the endpoint is absent it can keep authoring visible behind the 2. Unknown Dashboard IDs on PATCH
The native picker will only submit Dashboards it has read, so we can proceed safely for the normal path. There is still a delete-between-read-and-save race. Is the PATCH difference intentional, or should an unknown newly-added On the iOS side we will keep Lineup type fixed during edit, send only changed patchable fields, require the ETag from a fresh individual GET, surface |
|
Three answers, live on edge in v0.295.0. Daily and interval targetsOption 2, with a piece of option 1 next to it. The action endpoint resolves targets the way the engine does:
Worth knowing for your fixture: a Daily Lineup authored through Permission read
{"token_id": "ct_...", "scopes": ["devices:read", "..."], "settings_url": "/settings/companion"}Scopes are read off the token record, so a grant or withdrawal shows up on the next call. Unknown dashboard on PATCHNot intentional. It now answers 404 for a ErrataOne addition to the vendored spec copy, |
Uh oh!
There was an error while loading. Please reload this page.
Following our Discord conversation, where you said:
I have been looking at how that idea maps onto Tesserae’s newer unified Lineups model and the existing Companion trust boundary.
The original Companion proposal in Discussion #147 deliberately kept schedules, rotations, decks, and general editing in the web UI. Since then, Tesserae has consolidated those concepts behind the Lineups surface and unified deck storage through #167. The earlier read-only schedules proposal in #172 also predates that unification.
I would therefore like to revisit the boundary, but as a focused native Lineups workflow rather than reproducing the complete web administration UI.
Proposed native experience
The first authoring flow would mirror the four user-facing intents in Tesserae’s current setup wizard:
Show one dashboard at a set time
Keep one dashboard fresh
Cycle through several dashboards
Flip between dashboards by hand
The app would present those four simple choices rather than asking users to understand the historical Schedule / Rotation / Deck distinctions.
Tesserae would remain authoritative for storage, validation, dashboard binding, scheduling, rendering, conditions, quiet hours, and delivery.
Smallest complete first slice
My proposed first slice would include:
The app would not silently simplify an existing Lineup. A Lineup using conditions, fallback pages, priority mode, smart sync, advanced time windows, or another unsupported field would remain readable and controllable, but editing would open the web UI.
This is especially important because a partial client update must never reset server-owned fields it did not understand.
Proposed Companion API direction
I suggest exposing the user-facing resource as:
rather than exposing
DeckStoreor recreating separate schedule, rotation, and deck resources in the Companion API.A possible resource surface is:
Deletion could remain in the web UI for the first slice.
For creation, the request could use a simple authoring intent:
The server could translate that intent into the unified model.
For reads, I do not think the four authoring intents are sufficient to describe every existing record. The response may need to retain the underlying concepts, for example:
along with a server-derived indication such as:
The exact field names are open; the important behavior is that the server, not the app, decides whether a record can be safely edited through the basic native surface.
Capabilities and scopes
I suggest independently advertised capabilities, tentatively:
and explicit token scopes such as:
One permission question is important: existing Companion tokens currently carry an explicit persisted scope list. I do not think a new ability to modify household schedules should be silently granted to every previously paired client.
Would you prefer:
lineups:write;Dashboard and display binding
The current web wizard is display-aware and can bind an unassigned dashboard to the selected display during creation.
I think that behavior should remain server-owned rather than requiring the app to make several internal configuration calls.
A possible rule would be:
For example, creation might include an explicit intent such as:
{ "bind_unassigned_dashboards": true }Does that match the binding behavior you would want from a native client?
Concurrency and partial updates
For updates, I propose returning an
ETagor equivalent resource version and requiringIf-MatchonPATCH.If a Lineup has changed in the web UI since the app loaded it, the server should reject the stale update and let the app refresh. The app should never overwrite a newer server configuration.
A partial update must also preserve every omitted field. Reordering dashboards, renaming a Lineup, or changing its interval must not reset conditions, trigger details, smart-sync settings, or future fields unknown to the client.
Creation versus activation
I suggest keeping these separate:
An immediate push, step, or play action can reuse the existing Companion Jobs,
Idempotency-Key, History correlation, and explicit quiet-hours override pattern. Merely saving a definition should not unexpectedly refresh a physical display.Initially out of scope
To keep the first contract reviewable:
Those can remain available through the returned web-management URL.
Questions
/api/app/v1/lineupsthe right public resource name, with Deck remaining an internal model?lineups:read/lineups:writepermissions granted to existing and future paired clients?ETag/If-Matchacceptable for protecting web and app edits from overwriting each other?If this direction fits, I can prepare the same kind of contract-only Draft PR used for the previous Companion additions:
After the contract boundary is reviewed, the Tesserae adapter and capability-gated iOS surface can be implemented as separate slices.
All reactions