diff --git a/.scheduler-memory/session-mm3v12n7/retrieve.json b/.scheduler-memory/session-mm3v12n7/retrieve.json new file mode 100644 index 00000000..6c85587c --- /dev/null +++ b/.scheduler-memory/session-mm3v12n7/retrieve.json @@ -0,0 +1,18 @@ +{ + "cadence": "daily", + "operation": "retrieve", + "runId": "session-mm3v12n7", + "servicePath": "src/services/memory/index.js#getRelevantMemories", + "inputs": { + "agentId": "scheduler-memory-daily", + "promptPath": "", + "promptIntent": "scheduler memory retrieval", + "query": "scheduler | daily | agent | scheduler-memory-daily | intent | scheduler memory retrieval", + "events": 1 + }, + "outputs": { + "ingestedCount": 1, + "retrievedCount": 2 + }, + "status": "ok" +} \ No newline at end of file diff --git a/.scheduler-memory/session-mm3v12n7/retrieve.ok b/.scheduler-memory/session-mm3v12n7/retrieve.ok new file mode 100644 index 00000000..84b17199 --- /dev/null +++ b/.scheduler-memory/session-mm3v12n7/retrieve.ok @@ -0,0 +1 @@ +MEMORY_RETRIEVED diff --git a/.scheduler-memory/session-mm3v4xpe/store.json b/.scheduler-memory/session-mm3v4xpe/store.json new file mode 100644 index 00000000..5f214051 --- /dev/null +++ b/.scheduler-memory/session-mm3v4xpe/store.json @@ -0,0 +1,18 @@ +{ + "cadence": "daily", + "operation": "store", + "runId": "session-mm3v4xpe", + "servicePath": "src/services/memory/index.js#ingestEvents", + "inputs": { + "agentId": "scheduler-memory-daily", + "promptPath": "", + "promptIntent": "scheduler memory store", + "events": 0, + "sourceFile": "none" + }, + "outputs": { + "storedCount": 0, + "summaries": [] + }, + "status": "ok" +} \ No newline at end of file diff --git a/.scheduler-memory/session-mm3v4xpe/store.ok b/.scheduler-memory/session-mm3v4xpe/store.ok new file mode 100644 index 00000000..1e40bb67 --- /dev/null +++ b/.scheduler-memory/session-mm3v4xpe/store.ok @@ -0,0 +1 @@ +MEMORY_STORED diff --git a/embed.html b/embed.html index 3f20931d..eefea746 100644 --- a/embed.html +++ b/embed.html @@ -96,6 +96,7 @@ id="embedWatchButton" href="#" target="_blank" + rel="noopener noreferrer" class="flex items-center gap-2 rounded-full border border-white/20 bg-white/10 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-white/20 backdrop-blur-md" > Watch on diff --git a/js/app.js b/js/app.js index ac71b4b6..c4a54347 100644 --- a/js/app.js +++ b/js/app.js @@ -2,6 +2,7 @@ import { nostrClient } from "./nostrClientFacade.js"; import { recordVideoView } from "./nostrViewEventsFacade.js"; +import { getDTagValueFromTags } from "./nostr/nip71.js"; import { torrentClient } from "./webtorrent.js"; import { emit } from "./embedDiagnostics.js"; import { @@ -1370,18 +1371,7 @@ class Application { return this.similarContentController.extractDTagValue(tags); } // Fallback if controller not initialized (though it should be) - if (!Array.isArray(tags)) { - return ""; - } - for (const tag of tags) { - if (!Array.isArray(tag) || tag.length < 2) { - continue; - } - if (tag[0] === "d" && typeof tag[1] === "string") { - return tag[1]; - } - } - return ""; + return getDTagValueFromTags(tags); } deriveVideoPointerInfo(video) { diff --git a/tests/comment-avatar.test.mjs b/tests/comment-avatar.test.mjs index 7cb40364..4117765c 100644 --- a/tests/comment-avatar.test.mjs +++ b/tests/comment-avatar.test.mjs @@ -1,4 +1,4 @@ -import test from "node:test"; +import { describe, it } from "node:test"; import assert from "node:assert/strict"; import { normalizeCommentAvatarKey, @@ -6,151 +6,155 @@ import { registerCommentAvatarFailure, } from "../js/ui/components/video-modal/utils/commentAvatar.js"; -test("normalizeCommentAvatarKey returns empty string for non-string inputs", () => { - assert.equal(normalizeCommentAvatarKey(null), ""); - assert.equal(normalizeCommentAvatarKey(undefined), ""); - assert.equal(normalizeCommentAvatarKey(123), ""); - assert.equal(normalizeCommentAvatarKey({}), ""); - assert.equal(normalizeCommentAvatarKey([]), ""); -}); +describe("commentAvatar utils", () => { + describe("normalizeCommentAvatarKey", () => { + it("should return empty string for non-string inputs", () => { + assert.equal(normalizeCommentAvatarKey(null), ""); + assert.equal(normalizeCommentAvatarKey(undefined), ""); + assert.equal(normalizeCommentAvatarKey(123), ""); + assert.equal(normalizeCommentAvatarKey({}), ""); + }); -test("normalizeCommentAvatarKey returns empty string for empty or whitespace-only strings", () => { - assert.equal(normalizeCommentAvatarKey(""), ""); - assert.equal(normalizeCommentAvatarKey(" "), ""); - assert.equal(normalizeCommentAvatarKey("\t\n"), ""); -}); + it("should return empty string for empty or whitespace-only strings", () => { + assert.equal(normalizeCommentAvatarKey(""), ""); + assert.equal(normalizeCommentAvatarKey(" "), ""); + assert.equal(normalizeCommentAvatarKey("\t\n"), ""); + }); -test("normalizeCommentAvatarKey returns normalized lowercased string", () => { - assert.equal(normalizeCommentAvatarKey("FooBar"), "foobar"); - assert.equal(normalizeCommentAvatarKey(" BazQux "), "bazqux"); - assert.equal(normalizeCommentAvatarKey("ABC"), "abc"); -}); - -test("resolveCommentAvatarAsset returns default avatar if source is in failures", () => { - const failures = new Set(["https://example.com/failed.jpg"]); - const result = resolveCommentAvatarAsset({ - cache: new Map(), - failures, - defaultAvatar: "default.png", - pubkey: "somekey", - sanitizedPicture: "https://example.com/failed.jpg", + it("should return lowercased string for valid inputs", () => { + assert.equal(normalizeCommentAvatarKey("TestKey"), "testkey"); + assert.equal(normalizeCommentAvatarKey(" TestKey "), "testkey"); + assert.equal(normalizeCommentAvatarKey("TESTKEY"), "testkey"); + }); }); - assert.deepEqual(result, { url: "default.png", source: "" }); -}); + describe("resolveCommentAvatarAsset", () => { + const defaultAvatar = "default.png"; -test("resolveCommentAvatarAsset returns cached asset if pubkey matches and source matches", () => { - const cache = new Map(); - cache.set("somekey", { url: "cached.jpg", source: "https://example.com/pic.jpg" }); + it("should return default avatar if source is in failures", () => { + const failures = new Set(["bad-source.jpg"]); + const result = resolveCommentAvatarAsset({ + failures, + defaultAvatar, + pubkey: "pubkey1", + sanitizedPicture: "bad-source.jpg", + }); + assert.deepEqual(result, { url: defaultAvatar, source: "" }); + }); - const result = resolveCommentAvatarAsset({ - cache, - failures: new Set(), - defaultAvatar: "default.png", - pubkey: "somekey", - sanitizedPicture: "https://example.com/pic.jpg", + it("should return cached data if pubkey is in cache and source matches", () => { + const cache = new Map(); + cache.set("pubkey1", { url: "cached.jpg", source: "source.jpg" }); + const result = resolveCommentAvatarAsset({ + cache, + defaultAvatar, + pubkey: "pubkey1", + sanitizedPicture: "source.jpg", + }); + assert.deepEqual(result, { url: "cached.jpg", source: "source.jpg" }); + }); + + it("should return cached data if source is empty and pubkey is in cache", () => { + const cache = new Map(); + cache.set("pubkey1", { url: "cached.jpg", source: "source.jpg" }); + const result = resolveCommentAvatarAsset({ + cache, + defaultAvatar, + pubkey: "pubkey1", + sanitizedPicture: "", + }); + assert.deepEqual(result, { url: "cached.jpg", source: "source.jpg" }); + }); + + it("should update cache and return new source if cache miss (different source)", () => { + const cache = new Map(); + cache.set("pubkey1", { url: "old.jpg", source: "old-source.jpg" }); + const result = resolveCommentAvatarAsset({ + cache, + defaultAvatar, + pubkey: "pubkey1", + sanitizedPicture: "new-source.jpg", + }); + assert.deepEqual(result, { + url: "new-source.jpg", + source: "new-source.jpg", + }); + assert.equal(cache.get("pubkey1").url, "new-source.jpg"); + assert.equal(cache.get("pubkey1").source, "new-source.jpg"); + }); + + it("should update cache and return new source if cache miss (new pubkey)", () => { + const cache = new Map(); + const result = resolveCommentAvatarAsset({ + cache, + defaultAvatar, + pubkey: "pubkey2", + sanitizedPicture: "source.jpg", + }); + assert.deepEqual(result, { url: "source.jpg", source: "source.jpg" }); + assert.equal(cache.get("pubkey2").url, "source.jpg"); + }); + + it("should return default avatar if source is missing and not cached", () => { + const cache = new Map(); + const result = resolveCommentAvatarAsset({ + cache, + defaultAvatar, + pubkey: "pubkey3", + sanitizedPicture: "", + }); + assert.deepEqual(result, { url: defaultAvatar, source: "" }); + assert.deepEqual(cache.get("pubkey3"), { url: defaultAvatar, source: "" }); + }); }); - assert.deepEqual(result, { url: "cached.jpg", source: "https://example.com/pic.jpg" }); -}); + describe("registerCommentAvatarFailure", () => { + const defaultAvatar = "default.png"; -test("resolveCommentAvatarAsset returns cached asset if source is empty", () => { - const cache = new Map(); - cache.set("somekey", { url: "cached.jpg", source: "https://example.com/pic.jpg" }); + it("should ignore invalid inputs", () => { + const failures = new Set(); + registerCommentAvatarFailure({ failures, defaultAvatar, sourceUrl: null }); + registerCommentAvatarFailure({ failures, defaultAvatar, sourceUrl: "" }); + registerCommentAvatarFailure({ + failures, + defaultAvatar, + sourceUrl: defaultAvatar, + }); + assert.equal(failures.size, 0); + }); - const result = resolveCommentAvatarAsset({ - cache, - failures: new Set(), - defaultAvatar: "default.png", - pubkey: "somekey", - sanitizedPicture: "", + it("should add valid source to failures", () => { + const failures = new Set(); + registerCommentAvatarFailure({ + failures, + defaultAvatar, + sourceUrl: "bad.jpg", + }); + assert.ok(failures.has("bad.jpg")); + }); + + it("should update cache entries matching the failed source", () => { + const failures = new Set(); + const cache = new Map(); + cache.set("pubkey1", { url: "bad.jpg", source: "bad.jpg" }); + cache.set("pubkey2", { url: "good.jpg", source: "good.jpg" }); + + registerCommentAvatarFailure({ + cache, + failures, + defaultAvatar, + sourceUrl: "bad.jpg", + }); + + assert.ok(failures.has("bad.jpg")); + assert.deepEqual(cache.get("pubkey1"), { + url: defaultAvatar, + source: "", + }); + assert.deepEqual(cache.get("pubkey2"), { + url: "good.jpg", + source: "good.jpg", + }); + }); }); - - assert.deepEqual(result, { url: "cached.jpg", source: "https://example.com/pic.jpg" }); -}); - -test("resolveCommentAvatarAsset updates cache and returns new asset if not cached", () => { - const cache = new Map(); - const result = resolveCommentAvatarAsset({ - cache, - failures: new Set(), - defaultAvatar: "default.png", - pubkey: "newkey", - sanitizedPicture: "https://example.com/new.jpg", - }); - - assert.deepEqual(result, { url: "https://example.com/new.jpg", source: "https://example.com/new.jpg" }); - assert.ok(cache.has("newkey")); - assert.deepEqual(cache.get("newkey"), { url: "https://example.com/new.jpg", source: "https://example.com/new.jpg" }); -}); - -test("resolveCommentAvatarAsset updates cache when source changes for existing key", () => { - const cache = new Map(); - cache.set("somekey", { url: "old.jpg", source: "https://example.com/old.jpg" }); - - const result = resolveCommentAvatarAsset({ - cache, - failures: new Set(), - defaultAvatar: "default.png", - pubkey: "somekey", - sanitizedPicture: "https://example.com/new.jpg", - }); - - assert.deepEqual(result, { url: "https://example.com/new.jpg", source: "https://example.com/new.jpg" }); - assert.deepEqual(cache.get("somekey"), { url: "https://example.com/new.jpg", source: "https://example.com/new.jpg" }); -}); - -test("resolveCommentAvatarAsset uses default avatar when no source provided", () => { - const cache = new Map(); - const result = resolveCommentAvatarAsset({ - cache, - failures: new Set(), - defaultAvatar: "default.png", - pubkey: "somekey", - sanitizedPicture: null, - }); - - assert.deepEqual(result, { url: "default.png", source: "" }); - assert.deepEqual(cache.get("somekey"), { url: "default.png", source: "" }); -}); - -test("registerCommentAvatarFailure ignores invalid or default source urls", () => { - const failures = new Set(); - const cache = new Map(); - const defaultAvatar = "default.png"; - - registerCommentAvatarFailure({ cache, failures, defaultAvatar, sourceUrl: null }); - assert.equal(failures.size, 0); - - registerCommentAvatarFailure({ cache, failures, defaultAvatar, sourceUrl: "" }); - assert.equal(failures.size, 0); - - registerCommentAvatarFailure({ cache, failures, defaultAvatar, sourceUrl: "default.png" }); - assert.equal(failures.size, 0); -}); - -test("registerCommentAvatarFailure adds source to failures", () => { - const failures = new Set(); - const cache = new Map(); - const defaultAvatar = "default.png"; - const failedUrl = "https://example.com/fail.jpg"; - - registerCommentAvatarFailure({ cache, failures, defaultAvatar, sourceUrl: failedUrl }); - assert.ok(failures.has(failedUrl)); -}); - -test("registerCommentAvatarFailure updates cache entries matching the failed source", () => { - const failures = new Set(); - const cache = new Map(); - const defaultAvatar = "default.png"; - const failedUrl = "https://example.com/fail.jpg"; - - cache.set("user1", { url: failedUrl, source: failedUrl }); - cache.set("user2", { url: "other.jpg", source: "other.jpg" }); - - registerCommentAvatarFailure({ cache, failures, defaultAvatar, sourceUrl: failedUrl }); - - assert.ok(failures.has(failedUrl)); - assert.deepEqual(cache.get("user1"), { url: defaultAvatar, source: "" }); - assert.deepEqual(cache.get("user2"), { url: "other.jpg", source: "other.jpg" }); }); diff --git a/torch/package-lock.json b/torch/package-lock.json index b7ddc49c..f41c37be 100644 --- a/torch/package-lock.json +++ b/torch/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "nostr-tools": "2.23.1", + "nostr-tools": "^2.23.1", "ws": "8.19.0" }, "bin": { diff --git a/torch/package.json b/torch/package.json index 13272ae5..1c83a2bf 100644 --- a/torch/package.json +++ b/torch/package.json @@ -78,7 +78,7 @@ "torch:remove": "node bin/torch-lock.mjs remove" }, "dependencies": { - "nostr-tools": "2.23.1", + "nostr-tools": "^2.23.1", "ws": "8.19.0" }, "devDependencies": { diff --git a/torch/task-logs/daily/2026-02-26T19-35-26Z__scheduler-update-agent__completed.md b/torch/task-logs/daily/2026-02-26T19-35-26Z__scheduler-update-agent__completed.md new file mode 100644 index 00000000..b5a5fa26 --- /dev/null +++ b/torch/task-logs/daily/2026-02-26T19-35-26Z__scheduler-update-agent__completed.md @@ -0,0 +1,26 @@ +# Scheduler Update Agent - Completion Report + +**Status:** Success +**Timestamp:** 2026-02-26T19:35:26Z +**Agent:** scheduler-update-agent + +## Summary + +The agent roster and scheduler markdown files were verified against the prompt files on disk. No discrepancies were found, so no changes were applied. + +## Verification + +- **Daily Prompts:** 23 files found. +- **Weekly Prompts:** 21 files found. +- **Roster JSON:** Matches disk inventory exactly. +- **Scheduler Markdowns:** Tables match roster JSON exactly. +- **Validation Script:** `torch/scripts/validate-scheduler-roster.mjs` passed successfully. + +## Changes + +- None required. + +## Learnings + +- The `scheduler-update-agent` should always verify the existence of `nostr-tools` in `torch/node_modules` before attempting lock operations. +- The `validate-scheduler-roster.mjs` script is the authoritative source for verifying roster sync. diff --git a/verification/channel_profile.png b/verification/channel_profile.png new file mode 100644 index 00000000..ce2ade6c Binary files /dev/null and b/verification/channel_profile.png differ diff --git a/views/channel-profile.html b/views/channel-profile.html index f4b55d4b..32939924 100644 --- a/views/channel-profile.html +++ b/views/channel-profile.html @@ -135,6 +135,7 @@ id="channelWebsite" href="#" target="_blank" + rel="noopener noreferrer" class="mb-2 block text-info-strong hover:underline" >Website