From bb479db43cc0f8059bc3f09d6a47f1afc060e301 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 16:29:34 +0000 Subject: [PATCH 1/2] feat(extension): OP highlighting in the side panel (Phase 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Continue Phase 1 with the next roadmap item after clean reading mode. The parser already detects each post's role (op/mod/admin), following the OP through the whole thread, and render set a `data-role` attribute plus a generic badge — but every role looked identical and OP posts didn't stand out. This adds the visual treatment that is the feature: - render.ts maps roles to readable badge text (OP / Mod / Admin) and drops the badge for the unmarked "user" default. - sidepanel.html gives OP / mod / admin posts a colored left edge and a faint tint, plus a colored badge — signaled by text as well as color, so it doesn't rely on color alone. Docs (ROADMAP, Initial Plan checklist, app README) updated to mark OP highlighting done. render tests updated for the new badge text and a case asserting "user" posts get no badge. Verification: pnpm -r typecheck passes; pnpm test 76 pass (1 new); pnpm --filter @forumforge/extension build bundles dist/. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01WMZRs1b1oGfUuWrgzpStdm --- Initial Plan.md | 2 +- ROADMAP.md | 8 ++++--- apps/extension/README.md | 9 ++++++-- apps/extension/public/sidepanel.html | 34 +++++++++++++++++++++++++++- apps/extension/src/render.ts | 18 ++++++++++++--- apps/extension/test/render.test.ts | 17 +++++++++++++- 6 files changed, 77 insertions(+), 11 deletions(-) diff --git a/Initial Plan.md b/Initial Plan.md index e59f641..3040e24 100644 --- a/Initial Plan.md +++ b/Initial Plan.md @@ -436,7 +436,7 @@ Advanced JavaScript or TypeScript adapters should be clearly marked and reviewed ### Phase 1 — First useful version * [x] Clean reading mode -* [ ] OP highlighting +* [x] OP highlighting * [ ] New posts since last visit * [ ] Save comments * [ ] Local user notes diff --git a/ROADMAP.md b/ROADMAP.md index 9bca008..15094b2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -8,8 +8,10 @@ features before the adapter ecosystem, AI last and always optional. > **Status:** Phase 0 complete; Phase 1 underway. The foundation — core post > model, generic extractor, local storage layer — and the `apps/extension` MV3 > shell (background, on-demand content script, side panel) are built, unit-tested, -> and bundled via esbuild. Phase 1 has begun with **clean reading mode**: the side -> panel now renders each post's rich `contentHtml` through an allowlist sanitizer. +> and bundled via esbuild. Phase 1 has begun with **clean reading mode** (the side +> panel renders each post's rich `contentHtml` through an allowlist sanitizer) and +> **OP highlighting** (OP / mod / admin posts get a readable badge and a colored +> edge so the important voices stand out across the thread). ## Phase 0 — Foundation @@ -23,7 +25,7 @@ features before the adapter ecosystem, AI last and always optional. ## Phase 1 — First useful version - [x] Clean reading mode — sanitized rich `contentHtml` rendering in the side panel (`apps/extension`) -- [ ] OP highlighting +- [x] OP highlighting — OP / mod / admin posts get a readable badge and a colored edge in the side panel (`apps/extension`) - [ ] New posts since last visit - [ ] Save comments - [ ] Local user notes diff --git a/apps/extension/README.md b/apps/extension/README.md index f8dd45d..c665fa0 100644 --- a/apps/extension/README.md +++ b/apps/extension/README.md @@ -2,8 +2,9 @@ The ForumForge **browser extension** — a Manifest V3 app that turns the thread on the current page into a clean, readable list in a side panel. It is the Phase 0 -shell that later features (clean reading mode, OP highlighting, notes, saved -posts) build on. +shell that later features (notes, saved posts, new-post tracking) build on, and +it already ships the first Phase 1 reading features — clean reading mode and OP +highlighting. It wires together the foundation packages: the active page's DOM → [`@forumforge/parser`](../../packages/parser) → the @@ -28,6 +29,10 @@ side panel "Read this thread" ─▶ inject content.js (activeTab) ─▶ extrac - **`src/render.ts`** — builds the read-only view. Author, role and timestamp are written with `textContent`; the body renders the post's rich `contentHtml` through the sanitizer (clean reading mode), falling back to plain text. + **OP highlighting:** OP / moderator / admin posts get a readable role badge and + a colored edge (driven by a `data-role` attribute and styled in + `public/sidepanel.html`); the role is set by the parser and follows the OP + through the whole thread. The plain "user" role is left unmarked. - **`src/sanitize.ts`** — the **clean reading mode** sanitizer. Untrusted post HTML is parsed inertly and rebuilt from an allowlist of safe, semantic tags and attributes, so no script, inline handler, style, embed or unsafe URL survives diff --git a/apps/extension/public/sidepanel.html b/apps/extension/public/sidepanel.html index 0063f1a..f2381c4 100644 --- a/apps/extension/public/sidepanel.html +++ b/apps/extension/public/sidepanel.html @@ -46,6 +46,27 @@ padding: 10px 0; border-top: 1px solid #8883; } + /* OP highlighting: posts by an important voice stand out for the whole + thread, with a colored edge and a faint tint (not color alone — the + badge text carries the same signal). */ + .ff-post[data-role="op"], + .ff-post[data-role="mod"], + .ff-post[data-role="admin"] { + border-left: 3px solid #8884; + padding-left: 10px; + } + .ff-post[data-role="op"] { + border-left-color: #2563eb; + background: #2563eb14; + } + .ff-post[data-role="mod"] { + border-left-color: #16a34a; + background: #16a34a14; + } + .ff-post[data-role="admin"] { + border-left-color: #9333ea; + background: #9333ea14; + } .ff-post__meta { display: flex; gap: 8px; @@ -57,12 +78,23 @@ } .ff-post__role { font-size: 11px; - text-transform: uppercase; letter-spacing: 0.04em; padding: 1px 6px; border-radius: 999px; background: #8882; } + .ff-post[data-role="op"] .ff-post__role { + background: #2563eb; + color: #fff; + } + .ff-post[data-role="mod"] .ff-post__role { + background: #16a34a; + color: #fff; + } + .ff-post[data-role="admin"] .ff-post__role { + background: #9333ea; + color: #fff; + } .ff-post__time { color: #888; font-size: 12px; diff --git a/apps/extension/src/render.ts b/apps/extension/src/render.ts index b1c98c3..2b9fe75 100644 --- a/apps/extension/src/render.ts +++ b/apps/extension/src/render.ts @@ -1,7 +1,18 @@ -import type { ForumForgePost } from "@forumforge/core"; +import type { ForumForgePost, ForumRole } from "@forumforge/core"; import type { ExtractedThread } from "@forumforge/parser"; import { sanitizeHtml } from "./sanitize"; +/** + * Human-readable badge text per role. The OP highlighting feature surfaces who + * is who in a thread; "user" is the unmarked default, so it gets no badge. + */ +const ROLE_LABELS: Record = { + op: "OP", + mod: "Mod", + admin: "Admin", + user: "", +}; + /** * Build a clean, read-only view of an extracted thread. * @@ -53,10 +64,11 @@ function renderPost(doc: Document, post: ForumForgePost, baseUrl?: string): HTML author.textContent = post.author; meta.append(author); - if (post.role) { + const roleLabel = post.role ? ROLE_LABELS[post.role] : ""; + if (roleLabel) { const role = doc.createElement("span"); role.className = "ff-post__role"; - role.textContent = post.role; + role.textContent = roleLabel; meta.append(role); } diff --git a/apps/extension/test/render.test.ts b/apps/extension/test/render.test.ts index 024817b..fdf33a5 100644 --- a/apps/extension/test/render.test.ts +++ b/apps/extension/test/render.test.ts @@ -22,10 +22,25 @@ describe("renderThread", () => { expect(view.querySelector(".ff-thread__title")?.textContent).toBe("Monitor no signal"); expect(view.querySelectorAll(".ff-post")).toHaveLength(2); expect(view.querySelector(".ff-post__author")?.textContent).toBe("ada"); - expect(view.querySelector(".ff-post__role")?.textContent).toBe("op"); + expect(view.querySelector(".ff-post__role")?.textContent).toBe("OP"); expect(view.querySelector(".ff-post[data-role='op']")).not.toBeNull(); }); + it("shows a readable badge for highlighted roles but none for plain users", () => { + const thread: ExtractedThread = { + posts: [ + { id: "1", author: "ada", role: "mod", contentText: "Moved to the right forum." }, + { id: "2", author: "grace", role: "user", contentText: "Thanks." }, + ], + }; + const view = renderThread(freshDocument(), thread); + const badges = view.querySelectorAll(".ff-post__role"); + + // The moderator post is the only one with a badge; "user" is the unmarked default. + expect(Array.from(badges).map((b) => b.textContent)).toEqual(["Mod"]); + expect(view.querySelector(".ff-post[data-role='mod']")).not.toBeNull(); + }); + it("shows an empty state when there are no posts", () => { const view = renderThread(freshDocument(), { posts: [] }); expect(view.querySelector(".ff-empty")?.textContent).toBe("No posts found on this page."); From 097bb12cc22f4e37584bc146bcb3482bc1e68ca2 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 16:34:50 +0000 Subject: [PATCH 2/2] fix(extension): darken moderator badge for WCAG AA contrast The mod role badge used white 11px text on #16a34a (~3.3:1 contrast), below the WCAG AA 4.5:1 threshold for small text. Since the badge text is the non-color signal for OP highlighting, low-vision users could miss it. Use green-700 (#15803d, ~5:1). OP (#2563eb) and admin (#9333ea) badges already clear AA, so only the mod badge changes. Raised in PR review. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01WMZRs1b1oGfUuWrgzpStdm --- apps/extension/public/sidepanel.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/extension/public/sidepanel.html b/apps/extension/public/sidepanel.html index f2381c4..5701e1e 100644 --- a/apps/extension/public/sidepanel.html +++ b/apps/extension/public/sidepanel.html @@ -88,7 +88,9 @@ color: #fff; } .ff-post[data-role="mod"] .ff-post__role { - background: #16a34a; + /* Darker green than the edge so white badge text clears WCAG AA + contrast (~5:1) — the badge text is the non-color role signal. */ + background: #15803d; color: #fff; } .ff-post[data-role="admin"] .ff-post__role {