@unoverse-platform/studio 0.1.80 → 0.1.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unoverse-platform/studio",
3
- "version": "0.1.80",
3
+ "version": "0.1.81",
4
4
  "type": "module",
5
5
  "description": "Unoverse Studio: the local authoring app. Reads your own rx/ folder, no backend.",
6
6
  "scripts": {
@@ -137,6 +137,13 @@ export function applyServerMessage(store: ComponentStore, msg: ServerMessage, se
137
137
  else if (m.state === "ERROR" || m.state === "WORKFLOW_ERROR") store.setLifecycle("error");
138
138
  return;
139
139
  }
140
+ case "COMPONENTS_CLEAR": {
141
+ // Rule 5's empty delivery (STATE_MODEL §5, amended 2026-08-26): a turn that ran
142
+ // with cards enabled and surfaced none owns the screen — earlier-turn components
143
+ // in public states retract to inline. The sender decided WHEN; this only obeys.
144
+ store.supersedeAll(typeof msg.chatId === "string" ? msg.chatId : undefined);
145
+ return;
146
+ }
140
147
  // No AUDIO_STATE case: voice is a NATIVE SERVICE on the WS audio lane (useVoiceService),
141
148
  // NOT the structured MCP stream. Audio-state events never arrive here (UNOVERSE_SPEC §2e-1).
142
149
  case "APP_DATA": {
@@ -182,6 +182,31 @@ export class ComponentStore {
182
182
  * refresh is VISIBLE is the ladder's business alone (rule 4: higher states mask;
183
183
  * lower states update silently underneath).
184
184
  */
185
+ /**
186
+ * RULE 5's EMPTY DELIVERY (STATE_MODEL §5 rule 5, amended 2026-08-26): a discovery
187
+ * turn that ran with cards enabled and surfaced NONE still owns the screen — the
188
+ * server says so with COMPONENTS_CLEAR, and every earlier-turn component sitting in
189
+ * a public state retracts to inline. `supersede` for ALL states at once: same
190
+ * durable (`lifetime: "conversation"`) opt-out, same same-turn exemption. Rule 7
191
+ * stands — this rides the answer's delivery, never the guest's typing.
192
+ */
193
+ supersedeAll(arrivingChatId?: string): void {
194
+ let changed = false;
195
+ for (const [key, slice] of this.data) {
196
+ if (arrivingChatId && key.startsWith(`${arrivingChatId}:`)) continue;
197
+ if (slice.lifetime === "conversation") continue;
198
+ const v = (slice.view ?? slice.defaultState) as unknown;
199
+ if (typeof v !== "string" || !v || v === "inline") continue;
200
+ const next: Record<string, unknown> = { ...slice };
201
+ if ("view" in next || !("defaultState" in next)) next.view = "inline";
202
+ if ("defaultState" in next) next.defaultState = "inline";
203
+ this.data.set(key, next);
204
+ this.touches.set(key, ++this.touchSeq);
205
+ changed = true;
206
+ }
207
+ if (changed) this.bump();
208
+ }
209
+
185
210
  private supersede(arrivingChatId: string, view: unknown): void {
186
211
  if (typeof view !== "string" || !view || view === "inline") return;
187
212
  let changed = false;