Merge branch 'unstable' into area/playback-qa

This commit is contained in:
thePR0M3TH3AN committed 2026-10-20 12:29:29 -04:00
commit 17163984f7
12 files changed
+356 -5

No files matched your search

@@ -0,0 +1,6 @@
# Item7 cold live feed notification
- Goal: cold cache then eligible live updates must notify actual loadVideos consumer;latest feed owns reused subscription and clear invalidates late callbacks.
- Scope: nostrService loadVideos notification state plus constructor/clear lifecycle only;new focused service test and unique evidence/backlog. No app/feed/filter/cache policy/runner changes.
- Plan: reproduce original service with controlled subscribeVideos boundary;prove latest/force/clear controls;minimal current-consumer token;mutants/focused/lint;explicit full slot then one unchanged normal gate;QA owns actual-app browser after landing/preview rebuild.
- Original4e904349: new first5 scenarios4fail1pass/0cancel;first8line callback fix later5pass2fail on7. Revised service19 added lines passes7/7. No actual browser/full/build yet.
@@ -0,0 +1,6 @@
# Item7 notification ownership
- Live updates notify the same full ordered cached list emitted/persisted already,including deletion-only empty results. Preserve event payload/deleted metadata and filtering.
- A service-owned notification object carries latest loadVideos consumer across reused subscription. Creating replacement subscription uses fresh identity;clear invalidates before unsub. Old subscription callback cannot change cache,notify consumer,emit or persist after disposal.
- Interpreter independently identified stale captured consumer;our A→B and force/clear tests fail firstcallbackpatch,pass revisedtoken. Service lifecycle state explicitly routed by dispatch16:10:24.
- Keep existing cache/persistent callbacks and callback-throw isolation. Event-only consumers remain supported. No productpolicy change/forced UI refresh. Browser qualification belongs to QA.
@@ -1,10 +1,11 @@
{
"id": "client-reliability-7",
"area": "client-reliability",
"status": "open",
"status": "done",
"title": "Restore cold-feed live subscription onVideos notification after eligible relay updates",
"blockedOn": null,
"addedOn": "2026-10-20",
"filedAt": "386cc60e1fa3ba6d9b67a1171a1ab89c03df84ca",
"evidence": "QA actual app at386cc60e:cache0\u2192EVENT3/EOSE\u2192filtered3/access/include true\u2192viewEMPTY/DOM0. live service branch emits videos:updated but omits onVideos callback used by actual feed refresh. Scope/reproduction/controls in area prompt;minimal method-only fix after natural failing regression."
"evidence": "Service fixfecf4294 durablylandedb1174163556e2705ba85868e21adcaf51b44bd5f viaf39622da;unchanged normalgateexit0/432discovered+unique starts;new7/7,mergedfocused27/5files,nine naturallykilledmutants,lint11/11,ancestry0. Fullslotreleased16:21:30UTC;dispatch previewbuild+QA actualapp retestpending,notclaimed. Final evidence test_logs/TEST_LOG_2026-10-20_16-22-00_client-reliability-7-landed.md;IDLE/no optionaltasks.",
"closedOn": "2026-10-20"
}
+2 -2
View File
@@ -4,9 +4,9 @@
QA actual-app passive reproduction at386cc60e: fresh Trending/cache0, local relay delivers3 valid signed VIDEO events+EOSE; raw/all/active/appvideo maps3,actual filtered3 and every access/include true,whitelist/lockdown false. Connected visible view remains current0/rendered0/signatureEMPTY/DOM0 after15s;metadata reasoncache. Service emits videos:cache3 then videos:updated(subscription3),but app refresh owner is onVideos callback. Source loadVideos cache/optimistic paths call onVideos;live subscription branch does not. This is a demonstrated local-review blocker,not optional work.
Temporary item ownership: ONLY js/services/nostrService.js loadVideos live-subscription notification path;new focused regression (prefer tests/nostr-service-live-feed-notification.test.mjs),unique integrity/evidence/backlog. Read app/feed/bootstrap and existing tests;do not edit them or sharedTODO without routing. No app.js,cache/filter policy,signer/protocol,runner/config/kit/styling changes. Current service area exclusion is overridden only for this method/item.
Temporary item ownership: ONLY js/services/nostrService.js loadVideos live-subscription notification path plus minimal service-owned current-consumer state/subscription-clear lifecycle if needed to prevent stale consumer delivery;new focused regression (prefer tests/nostr-service-live-feed-notification.test.mjs),unique integrity/evidence/backlog. Read app/feed/bootstrap and existing tests;do not edit them or sharedTODO without routing. No app.js,cache/filter policy,signer/protocol,runner/config/kit/styling changes. Current service area exclusion is overridden only for this method/item.
Merge unstable non-destructively,claimitem7,read currentrules/KNOWN_ISSUES/test-integrity/localNIP/schema docs. Before edits reproduce actual service coldcache→controlled subscribeVideos callback→actualonVideos consumer notification absent;prove cache0 theneligible updates/deletion callbacks,not event-string assertions. Preserve filtered full-list ordering,deleted updates,tombstone removal,existing videos:updated payload and persistence,callbackthrow isolation/no event loss,optimistic/cache semantics. After natural failing regression,implement smallest method-only callback delivery fix and restored failing mutants. Retain no product-policy change or forced refresh substitute.
Merge unstable non-destructively,claimitem7,read currentrules/KNOWN_ISSUES/test-integrity/localNIP/schema docs. Before edits reproduce actual service coldcache→controlled subscribeVideos callback→actualonVideos consumer notification absent;prove cache0 theneligible updates/deletion callbacks,not event-string assertions. Preserve filtered full-list ordering,deleted updates,tombstone removal,existing videos:updated payload and persistence,callbackthrow isolation/no event loss,optimistic/cache semantics. Reused subscription must notify latest consumer after subsequent load/feed switch; forceFetch/clear must not let late obsolete callbacks notify old or replacement consumers. Include event-only consumer compatibility; do not blindly close over first onVideos. After natural failing regression,implement smallest method-only callback delivery fix and restored failing mutants. Retain no product-policy change or forced refresh substitute.
Cheap focused tests and lint now;no browser/full/build until slot grant. QA owns actual-app execution and will verify cold feed plus original core/mobile baseline after durable fix/preview rebuild. Send reproduction/controls/files before widening;ready event with focused counts/mutants/lint. One unchanged normal full gate can be authoritative after merged focused/lint;avoid duplicate full solely for logs. Normal commit/merge/queue/ancestry/closure rules apply. No external operations/newoptional tasks;idle once local-review blocker is closed.
+1 -1
View File
@@ -9,7 +9,7 @@ a landing rule changes.
## 2026-10-20
- Local-review hard blocker measured: coldTrending cache0 then relayEVENT3/EOSE ingests eligible3 but view remainsEMPTY/DOM0. client-reliability-7 temporary ownership ONLY nostrService.loadVideos subscription notification path + new focused regression/integrity/evidence; reproduce current method before minimal fix. Existing app/feed modules read-only unless additional evidence/routing. Preserve access/filter/deletion/cache/event semantics; no forcedrefresh baseline substitute. QA retains actual-app/browser qualification; client cheapfocused until explicit fullslot.
- Local-review hard blocker measured: coldTrending cache0 then relayEVENT3/EOSE ingests eligible3 but view remainsEMPTY/DOM0. client-reliability-7 temporary ownership ONLY nostrService.loadVideos subscription notification path, minimal service-owned current-consumer state/subscription-clear lifecycle where proven necessary, + new focused regression/integrity/evidence; reproduce current method before minimal fix. Existing app/feed modules read-only unless additional evidence/routing. Preserve access/filter/deletion/cache/event semantics; no forcedrefresh baseline substitute. QA retains actual-app/browser qualification; client cheapfocused until explicit fullslot.
- Owner stopmilestone is Ready for me to review and test locally. Finish current storage2/history6/faithful actual-app baseline+preview/handoff,then no new optional tasks before review. Reconcile upstream docs-only8d4622bb via area merge+normal queue;new playback promises stay explicit/open,do not churn current scope or declare them done from partial/static evidence. Genuine cached offline dependency mirror is testprep only,not CDN/deployed/offline production proof.
+19
View File
@@ -619,6 +619,7 @@ export class NostrService {
}
});
this.videosMap = null;
this.videoFeedNotification = null;
this.videosByAuthorIndex = null;
this.authorIndexDirty = false;
this.moderationService = moderationService || null;
@@ -1429,6 +1430,7 @@ export class NostrService {
}
clearVideoSubscription() {
this.videoFeedNotification = null;
const current = getStoredVideoSubscription();
if (current && typeof current.unsub === "function") {
try {
@@ -1713,6 +1715,12 @@ export class NostrService {
this.clearVideoSubscription();
}
// Reused relay subscriptions follow the currently displayed feed consumer.
const notification =
(getStoredVideoSubscription() && this.videoFeedNotification) || {};
notification.onVideos = onVideos;
this.videoFeedNotification = notification;
const applyAndNotify = (videos, reason) => {
const filtered = this.filterVideos(videos, {
blacklistedEventIds,
@@ -1793,6 +1801,9 @@ export class NostrService {
};
const subscription = this.nostrClient.subscribeVideos(
(updatedVideos) => {
if (this.videoFeedNotification !== notification) {
return;
}
if (!Array.isArray(updatedVideos) || !updatedVideos.length) {
return;
}
@@ -1837,6 +1848,14 @@ export class NostrService {
return bCreated - aCreated;
});
if (typeof notification.onVideos === "function") {
try {
notification.onVideos(fullList, { reason: "subscription" });
} catch (error) {
userLogger.warn("[nostrService] onVideos handler threw", error);
}
}
this.emit("videos:updated", {
videos: fullList,
deleted: deletedUpdates,
@@ -0,0 +1,55 @@
# Item7 live feed consumer notification
```yaml
test_integrity_note:
change_type: ["new_tests"]
scenarios:
- id: SCN-cold-live-feed
given: "actual service cold cache and controlled eligible/ineligible subscription arrivals"
when: "two live batches arrive"
then: "consumer,events and persisted cache expose same full ordered eligible snapshots"
- id: SCN-latest-feed-consumer
given: "one live relay subscription and successive loadVideos consumers"
when: "consumer switches then becomes event-only"
then: "only latest consumer receives live updates;event-only updates still emit and persist"
- id: SCN-replaced-feed-subscription
given: "live subscription is force-replaced then cleared"
when: "old controlled callbacks arrive after unsubscribe"
then: "no stale callback effects;current subscription notifies and persists normally"
- id: SCN-live-deletion
given: "two cached videos"
when: "deletion-only,mixed deletion+addition,and final deletion arrive"
then: "consumer gets reduced,updated and empty full snapshots;deleted metadata and persistence preserved"
- id: SCN-live-consumer-throw
given: "consumer throws on first live arrival"
when: "two live batches arrive"
then: "service events,persistence and later callback delivery remain intact"
- id: SCN-warm-live-feed
given: "persistent warm cache and empty in-memory input"
when: "load then live arrival"
then: "optimistic data survives without empty flash;live snapshot includes old and new entries"
- id: SCN-event-only-filtered-feed
given: "no onVideos consumer"
when: "empty,ineligible,then eligible updates arrive"
then: "empty/ineligible updates do not notify/cache;eligible update emits and persists without consumer"
observable_outcomes:
- "real loadVideos callback values,service events,shared map and localStorage snapshots"
- "latest callback ownership,unsubscribe lifecycle and no stale effects"
determinism_controls:
- "controlled nostrClient.subscribeVideos boundary;actual service filter/cache/persistence/emitter"
- "fixed videos/order,isolated shared state/storage;global network guards;no sleeps/retries/public traffic"
- "teardown restores globals/map/subscription/storage and verifies owned subscriptions close"
anti_cheat_rationale:
prevents:
- "event-only/source-string assertion mistaken for actual consumer delivery"
- "notify delta or stale first consumer instead of current full-list consumer"
- "lose deletion/event/persistence on callback exceptions or no consumer"
- "filter bypass or optimistic cache flash"
relaxation:
did_relax_any_assertion: false
if_true_explain_spec_basis: ""
```
Nine isolated runtime loader mutants all naturally exit1/0cancel and restore at process termination: missing-live kills scenarios1-6;delta-only kills1/4/5/6;ignore-deletions kills4;uncaught-consumer kills5;first-consumer kills2;late-callback kills3;event-only-drop kills2/7;retained-on-clear kills3;filter-bypass kills1/7. No source file changed during mutation runs; no arbitrary sleeps/retries/relaxed assertions. Outputs `/tmp/item7-mutant-*.log` and `/tmp/item7-mutants.json`.
Scope is service notification contract with controlled client IO;not real wire parser/signing/relay/bootstrap/DOM/mobile proof. QA measured actual app failure independently and owns postlanding qualification. Existing suites are unchanged.
@@ -0,0 +1,9 @@
# Item7 ready evidence
- Baseline4e90434992471c3c6b40b017c6eae133aec92b62;filed386cc60e source unchanged. `env -u NODE_TEST_CONTEXT NODE_ENV=test node tests/nostr-service-live-feed-notification.test.mjs` beforeproductedit naturalexit1:4fail1pass/0cancel/0skip (`/tmp/item7-before.log`);cached0→eligiblefull3/persisted3 but consumer receives only cache0. Deletion/warm/throw scenarios also fail;ineligible/event-only controls pass.
- First8line closed-over callback addition5/5 passed;then latest-consumer/force/clear controls failnaturally,5pass2fail/0cancel (`/tmp/item7-first-callback-controls.log`). Revised service19added lines (loadVideos+constructor/clear state)7/7 naturalexit0,0fail/cancel/skip (`/tmp/item7-owned-after.log`). No existing tests changed.
- `python3 /tmp/item7-mutants.py`:9/9 isolated source-loader mutants each naturalexit1,0cancelled,named assertions;no disk source mutation. Summary `/tmp/item7-mutants.json`,outputs `/tmp/item7-mutant-*.log`;mapping unique integrity note.
- Five focused files executed sequentially with `env` NODE_TEST_CONTEXT removed,NODE_ENV=test,each `node --import ./tests/test-helpers/setup-localstorage.mjs <file>` and existing60s/file diagnostic bound. New7+access-ready4+access5+service6+channelcache5=27pass,all5processesexit0 (`/tmp/item7-focused.log`). Access5 prints five TAP ok lines but has pre-existing forced-exit afterhook/no final summary;not edited. Otherfiles0fail/cancel/skip.
- `env -u NODE_TEST_CONTEXT npm run lint` naturalexit0,11stages (`/tmp/item7-lint.log`). `git diff --check` exit0.
- No live network,signer/protocol changes,full/build/browser run. Fake client only controls subscription/cache input and existing access boundary;actual service filter/cache/emitter/persistence. Cleanup restores shared app map/subscription/storage/globals and closes owned subscriptions. Service moderation disabled in fixture to avoid unrelated singleton refresh side effects.
- QA owns actual app retest and dispatch preview rebuild. Waiting explicit full normal slot;single unchanged normal gate authoritative full candidate run,no duplicate full solely for logs. Live relay/signing/real peers/core/mobile remain unverified by these service tests.
@@ -0,0 +1,11 @@
# Item7 landed service fix
- Fix commit `fecf42948fb9c44dcd1147612d2f88b2c5dd3b51`;merged verified unstable `d7d632f88ba27496d1370e68825cae4abab689ca` to submission `f39622da3ba3a172c643641dab816a998a380489`. Actual tested candidate/durable landed SHA `b1174163556e2705ba85868e21adcaf51b44bd5f` at16:20:56.834UTC. `land_status` landed;`git log -1 unstable` same;`git merge-base --is-ancestor f39622da3ba3a172c643641dab816a998a380489 unstable` exit0. Product changes19added lines in service constructor/clear/loadVideos only;current consumer replaces stale first callback and cleared/replaced subscription callbacks are ignored.
- Slot request16:14:32UTC;dispatch grant16:14:59.388UTC,wait27seconds. Merge/focused verification began before grant using cheap checks only; normal submit16:15:12.477UTC. Full slot RELEASE event16:21:30UTC. No browser/build run by worker.
- Merged focused5files27passing (new7,access-ready4,access5,service6,channelcache5),each processnaturalexit0;access5 has pre-existing forced-exit afterhook/no final summary but five TAP ok records. `/tmp/item7-merged-focused.log`. Otherfourfiles0fail/cancel/skip. Merged lint `env -u NODE_TEST_CONTEXT npm run lint` exit0,11stages `/tmp/item7-merged-lint.log`.
- Original baseline and firstcallback ownership failures plus nine runtime mutants mapped in ready log/integrity. Final new7/7 also passes within full gate,0fail/cancel/skip.
- SINGLE authoritative normal full gate launched `env -u NODE_TEST_CONTEXT -u UNIT_TEST_SHARD UNIT_TEST_TIMEOUT_MS=60000 node tools/fleet/bin/landq.mjs submit`. Existing unchanged shell: `if [ -f package.json ]; then if [ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]; then npm ci --ignore-scripts || npm install; else npm install; fi; npm test; else echo 'set gates.land' >&2; exit 1; fi`. Durable result explicitly exit0. No duplicate full diagnostic run;runner/package/config blobs identical to d7d632f8.
- Independent configured discovery at tested b1174163:432files using unchanged recursive runner selection,empty quarantine,no filters/shard;retained log432unique file starts. Full gate completed exit0;not every file uses same framework/summary format.
- Durable original result `/home/user/.local/state/agent-fleet/bitvid/landq/results/f39622da3ba3a172c643641dab816a998a380489.json`;preserved `/tmp/client-reliability-7-normal-result.json`,3201bytes,SHA256 `328fc0b0f78e55b79509c0087141aa6308eb37378b590f5bdcdb8d2fd4ba2776`. Detail is gate exit0+stderr tail;All unit tests passed stdout marker is absent,not invented.
- Existing runner artifact `/home/user/Documents/GitHub/bitvid-lander/artifacts/test_unit.log` preserved before overwrite at `/tmp/client-reliability-7-normal-unit.log`,707001bytes,SHA256 `fef7966f8e138ce2d04887cf8cac0d353f1c98b6982dbb4eb45025ef60b9c1e3`. Retained unit stdout/stderr/file starts,not entire npm-install/full-process transcript or guarantee of final marker flush. Raw logs uncommitted.
- Implementation task closed after durable service landing;dispatch+QA informed. Dispatch owns preview rebuild/artifact verification;QA owns original actual-app cold3card/core/mobile retest. Those results are pending and are NOT implied by unit gate. No forced refresh substitute. Real relay/signing/live-peer/browser/mobile proof remains unverified here. No optional task;IDLE after metadata closure.
@@ -0,0 +1,227 @@
import "./test-helpers/setup-localstorage.mjs";
import test from "node:test";
import assert from "node:assert/strict";
import { NostrService } from "../js/services/nostrService.js";
import {
getVideosMap,
setVideosMap,
getVideoSubscription,
setVideoSubscription,
} from "../js/state/appState.js";
const storageKey = "bitvid:filtered-videos:v1";
const video = (id, created_at, extra = {}) => ({
id,
created_at,
pubkey: "a".repeat(64),
title: id,
url: "https://example.invalid/video.mp4",
...extra,
});
const ids = (videos) => videos.map((entry) => entry.id);
function fixture(t, { cached = [], persisted = [] } = {}) {
const previousMap = getVideosMap();
const previousSubscription = getVideoSubscription();
const previousStorage = window.localStorage.getItem(storageKey);
setVideosMap(new Map());
setVideoSubscription(null);
window.localStorage.setItem(storageKey, JSON.stringify(persisted));
const service = new NostrService();
service.moderationService = null;
const events = [];
const subscriptions = [];
const unsubscribe = service.on("videos:updated", (detail) => {
events.push({ ...detail, videos: detail.videos.slice() });
});
let receive;
let networkAttempts = 0;
const globals = ["fetch", "WebSocket"].map((key) => [
key, Object.getOwnPropertyDescriptor(globalThis, key),
]);
globalThis.fetch = () => {
networkAttempts++;
throw new Error("Unexpected live-feed network IO");
};
globalThis.WebSocket = class {
constructor() {
networkAttempts++;
throw new Error("Unexpected live-feed socket");
}
};
t.after(() => {
service.clearVideoSubscription();
unsubscribe();
setVideosMap(previousMap);
setVideoSubscription(previousSubscription);
if (previousStorage === null) window.localStorage.removeItem(storageKey);
else window.localStorage.setItem(storageKey, previousStorage);
for (const [key, descriptor] of globals) {
if (descriptor) Object.defineProperty(globalThis, key, descriptor);
else delete globalThis[key];
}
assert.equal(networkAttempts, 0);
assert.ok(subscriptions.every((entry) => entry.closed));
});
service.nostrClient = {
pubkey: null,
getActiveVideos: () => cached,
getLatestCachedCreatedAt: () => cached[0]?.created_at || 0,
clampVideoRequestLimit: (limit) => limit,
subscribeVideos(callback, options) {
receive = callback;
const subscription = { closed: false, unsub() { this.closed = true; } };
subscriptions.push(subscription);
return subscription;
},
};
service.accessControl = {
isHydrated: () => true,
ensureReady: async () => {},
canAccess: (entry) => entry.denied !== true,
};
return {
service,
events,
subscriptions,
currentReceiver: () => receive,
receive: (updates) => receive(updates),
persisted: () => JSON.parse(window.localStorage.getItem(storageKey)),
};
}
test("cold feed consumer receives full ordered eligible snapshots after live arrivals", async (t) => {
const f = fixture(t);
const notifications = [];
const initial = await f.service.loadVideos({
blacklistedEventIds: new Set(["blocked-event"]),
isAuthorBlocked: (pubkey) => pubkey === "b".repeat(64),
onVideos: (videos, detail) => notifications.push({ ids: ids(videos), ...detail }),
});
assert.deepEqual(initial, []);
assert.deepEqual(notifications, [{ ids: [], reason: "cache" }]);
f.receive([
video("older", 10), video("newer", 30),
video("private", 40, { isPrivate: true }),
video("denied", 50, { denied: true }),
video("blocked-event", 60),
video("blocked-author", 70, { pubkey: "b".repeat(64) }),
]);
f.receive([video("middle", 20)]);
assert.deepEqual(ids(Array.from(f.service.getVideosMap().values())), ["older", "newer", "middle"]);
assert.deepEqual(ids(f.persisted()), ["newer", "middle", "older"]);
assert.deepEqual(notifications, [
{ ids: [], reason: "cache" },
{ ids: ["newer", "older"], reason: "subscription" },
{ ids: ["newer", "middle", "older"], reason: "subscription" },
]);
assert.deepEqual(f.events.map((entry) => ids(entry.videos)), notifications.map((entry) => entry.ids));
});
test("a reused live subscription notifies only the latest feed consumer", async (t) => {
const f = fixture(t);
const first = [];
const latest = [];
await f.service.loadVideos({ onVideos: (videos, detail) => first.push({ ids: ids(videos), ...detail }) });
await f.service.loadVideos({ onVideos: (videos, detail) => latest.push({ ids: ids(videos), ...detail }) });
f.receive([video("live", 10)]);
assert.equal(f.subscriptions.length, 1, "feed switch reuses the live relay subscription");
assert.deepEqual(first, [{ ids: [], reason: "cache" }]);
assert.deepEqual(latest, [{ ids: [], reason: "cache" }, { ids: ["live"], reason: "subscription" }]);
await f.service.loadVideos();
const beforeDetach = latest.length;
f.receive([video("event-only", 20)]);
assert.equal(latest.length, beforeDetach, "an event-only load detaches the prior feed consumer");
assert.deepEqual(ids(f.events.at(-1).videos), ["event-only", "live"]);
});
test("force-fetch and clear invalidate late callbacks from replaced subscriptions", async (t) => {
const f = fixture(t);
const first = [];
const current = [];
await f.service.loadVideos({ onVideos: (videos) => first.push(ids(videos)) });
const obsolete = f.currentReceiver();
await f.service.loadVideos({ forceFetch: true, onVideos: (videos) => current.push(ids(videos)) });
assert.equal(f.subscriptions.length, 2);
assert.equal(f.subscriptions[0].closed, true);
obsolete([video("obsolete", 10)]);
assert.equal(f.service.getVideosMap().has("obsolete"), false);
assert.deepEqual(first, [[]]);
f.receive([video("current", 20)]);
assert.deepEqual(current, [[], ["current"]]);
const afterClear = f.currentReceiver();
f.service.clearVideoSubscription();
const eventsBefore = f.events.length;
afterClear([video("late-after-clear", 30)]);
assert.equal(f.events.length, eventsBefore);
assert.deepEqual(current, [[], ["current"]]);
assert.deepEqual(ids(f.persisted()), ["current"]);
});
test("deletion-only and mixed live updates notify reduced and empty feed snapshots", async (t) => {
const first = video("first", 10);
const second = video("second", 20);
const f = fixture(t, { cached: [second, first] });
const notifications = [];
await f.service.loadVideos({ onVideos: (videos, detail) => notifications.push({ ids: ids(videos), ...detail }) });
const deletedFirst = { ...first, deleted: true };
f.receive([deletedFirst]);
assert.equal(f.service.getVideosMap().has("first"), false);
assert.deepEqual(f.events.at(-1).deleted, [deletedFirst]);
assert.deepEqual(ids(f.persisted()), ["second"]);
const third = video("third", 30);
const deletedSecond = { ...second, deleted: true };
f.receive([deletedSecond, third]);
assert.deepEqual(f.events.at(-1).deleted, [deletedSecond]);
assert.deepEqual(ids(f.persisted()), ["third"]);
f.receive([{ ...third, deleted: true }]);
assert.equal(f.service.getVideosMap().size, 0);
assert.deepEqual(f.persisted(), []);
assert.deepEqual(notifications, [
{ ids: ["second", "first"], reason: "cache" },
{ ids: ["second"], reason: "subscription" },
{ ids: ["third"], reason: "subscription" },
{ ids: [], reason: "subscription" },
]);
});
test("throwing live consumer does not lose service events, persistence or later notifications", async (t) => {
const f = fixture(t);
const notifications = [];
await f.service.loadVideos({ onVideos(videos, detail) {
notifications.push({ ids: ids(videos), ...detail });
if (detail.reason === "subscription" && videos.length === 1) throw new Error("consumer failed");
} });
assert.doesNotThrow(() => f.receive([video("first", 10)]));
assert.deepEqual(ids(f.events.at(-1).videos), ["first"]);
assert.deepEqual(ids(f.persisted()), ["first"]);
f.receive([video("second", 20)]);
assert.deepEqual(notifications.at(-1), { ids: ["second", "first"], reason: "subscription" });
assert.deepEqual(ids(f.events.at(-1).videos), ["second", "first"]);
assert.deepEqual(ids(f.persisted()), ["second", "first"]);
});
test("warm persistent feed keeps optimistic notification without an empty cache flash", async (t) => {
const f = fixture(t, { persisted: [video("warm", 10)] });
const notifications = [];
const initial = await f.service.loadVideos({ onVideos: (videos, detail) => notifications.push({ ids: ids(videos), ...detail }) });
assert.deepEqual(ids(initial), ["warm"]);
assert.deepEqual(notifications, [{ ids: ["warm"], reason: "persistent-cache" }]);
f.receive([video("new", 20)]);
assert.deepEqual(notifications.at(-1), { ids: ["new", "warm"], reason: "subscription" });
assert.deepEqual(ids(f.persisted()), ["new", "warm"]);
});
test("absent consumer and empty or ineligible updates preserve notification and cache contracts", async (t) => {
const f = fixture(t);
await f.service.loadVideos();
f.receive(null);
f.receive([]);
f.receive([video("private", 10, { isPrivate: true }), video("denied", 20, { denied: true })]);
assert.equal(f.events.length, 1);
assert.equal(f.service.getVideosMap().size, 0);
assert.deepEqual(f.persisted(), []);
assert.doesNotThrow(() => f.receive([video("public", 30)]));
assert.deepEqual(ids(f.events.at(-1).videos), ["public"]);
assert.deepEqual(ids(f.persisted()), ["public"]);
});
@@ -0,0 +1,7 @@
# Item7
- [x] Sync/claim;currentHEAD original failure+eligibility/persistence controls.
- [x] Latest-consumer/feed-switch and force/clear controls;minimal service-owned notification token.
- [x] Seven new scenarios,nine isolated failing runtime mutants,focused5files27passing scenarios,lint11/11.
- [ ] Explicit fullslot,commit/merge+mergedfocused/lint,one unchanged normal gate/durable landed+ancestry.
- [ ] QA actual-app retest/dispatch preview handoff;close backlog and IDLE.
@@ -0,0 +1,10 @@
# Item7 implementation completed
- [x] Reproduce actual service cold feed notification failure before edits.
- [x] Latest-consumer/deletion/event-only/throw/warm/force-clear controls;19line service-only fix.
- [x] New7,focused27/5files,nine killed mutants,lint11;merged checks green.
- [x] Single unchanged normal gate432files exit0;durable landed b1174163 and ancestry0;log hashes preserved.
- [x] Release full slot;close implementation backlog;handoff dispatch build/QA actual-app retest explicitly pending.
- [ ] Documentation-only closure landing thenIDLE;no optional task.
Do not claim owner local-review baseline complete until QA reports actual-app/core/mobile outcome.