From 63cedec678fbaade5a72ea7cc04a14a551e9bb47 Mon Sep 17 00:00:00 2001 From: Bersabel Tadesse Date: Wed, 19 Aug 2026 06:35:03 -0700 Subject: [PATCH] Restyle comment inputs with BB tokens --- plugins/timeline-comments/app-style.test.ts | 33 ++++- plugins/timeline-comments/app.css | 60 +++++++- .../browser-check/harness.ts | 64 +++++++- .../timeline-comments/browser-check/run.mjs | 13 +- plugins/timeline-comments/dist/app.css | 139 +++++++++++++++++- 5 files changed, 290 insertions(+), 19 deletions(-) diff --git a/plugins/timeline-comments/app-style.test.ts b/plugins/timeline-comments/app-style.test.ts index 4ef39fd..bcaee5b 100644 --- a/plugins/timeline-comments/app-style.test.ts +++ b/plugins/timeline-comments/app-style.test.ts @@ -55,17 +55,36 @@ describe("timeline comments visual contract", () => { expect(css).toContain("@media (prefers-reduced-motion: reduce)"); }); - it("leaves comment input focus indication to the browser", () => { - expect(css).not.toContain("outline: none !important;"); - expect(css).not.toMatch( - /\.bb-comments-textarea,[\s\S]*?\.bb-comments-panel textarea \{[^}]*outline:\s*none;/u, + it("uses BB input tokens for comment input states", () => { + expect(css).toMatch( + /\.bb-comments-mention-input \{[\s\S]*border: 1px solid var\(--input, var\(--border\)\);[\s\S]*background: var\(--background\);/u, + ); + expect(css).toMatch( + /\.bb-comments-mention-input:focus-within \{[\s\S]*border-color: var\(--ring\);[\s\S]*0 0 0 1px var\(--ring\);/u, + ); + expect(css).toMatch( + /\.bb-comments-mention-input:has\(\.bb-comments-error\) \{[\s\S]*var\(--destructive-text/u, + ); + expect(css).toMatch( + /\.bb-comments-mention-input\[aria-busy="true"\] \.bb-comments-input-surface,[\s\S]*var\(--surface-recessed\) 55%/u, ); - expect(css).not.toContain(".bb-comments-mention-input:has("); - expect(css).not.toMatch( - /\.bb-comments-panel textarea:focus-visible \{[^}]*outline:/u, + expect(css).toMatch( + /\.bb-comments-mention-input textarea:focus,[\s\S]*outline: none;[\s\S]*box-shadow: none;/u, ); expect(css).toMatch( /\.bb-comments-composer-action:focus-visible,[\s\S]*color-mix\(in oklab, var\(--foreground\) 15%, transparent\)/u, ); }); + + it("keeps the pre-restyle submit control unchanged", () => { + expect(css).toMatch( + /\.bb-comments-submit-shortcut \{\n position: static;\n display: flex;\n min-width: 27px;\n height: 18px;[\s\S]*? cursor: pointer;\n\}/u, + ); + expect(css).toMatch( + /\.bb-comments-submit-shortcut:hover:not\(:disabled\) \{\n background: var\(--surface-recessed\);\n color: var\(--foreground\);\n\}/u, + ); + expect(css).toMatch( + /\.bb-comments-submit-shortcut:disabled \{\n cursor: default;\n opacity: 0\.3;\n\}/u, + ); + }); }); diff --git a/plugins/timeline-comments/app.css b/plugins/timeline-comments/app.css index b3ca68b..487fb95 100644 --- a/plugins/timeline-comments/app.css +++ b/plugins/timeline-comments/app.css @@ -137,7 +137,9 @@ box-sizing: border-box; padding: 5px 6px; overflow: visible; - transition: border-color 120ms ease; + transition: + border-color 120ms ease, + box-shadow 120ms ease; } .bb-comments-textarea, .bb-comments-reply-input, @@ -517,6 +519,60 @@ min-width: 0; flex: 1; flex-direction: column; + border: 1px solid var(--input, var(--border)); + border-radius: 8px; + background: var(--background); + box-shadow: 0 1px 2px + color-mix(in oklab, var(--foreground) 8%, transparent); + transition: + border-color 120ms ease, + background-color 120ms ease, + box-shadow 120ms ease; +} +.bb-comments-composer .bb-comments-mention-input { + border: 0; + border-radius: 5px; + background: transparent; + box-shadow: none; +} +.bb-comments-mention-input:focus-within { + border-color: var(--ring); + box-shadow: 0 0 0 1px var(--ring); +} +.bb-comments-composer:focus-within { + border-color: var(--ring); + box-shadow: + 0 0 0 1px var(--ring), + 0 6px 18px color-mix(in oklab, var(--foreground) 12%, transparent); +} +.bb-comments-composer .bb-comments-mention-input:focus-within { + border-color: transparent; + box-shadow: none; +} +.bb-comments-mention-input:has(.bb-comments-error) { + border-color: var(--destructive-text, var(--foreground)); +} +.bb-comments-composer:has(.bb-comments-error) { + border-color: var(--destructive-text, var(--foreground)); +} +.bb-comments-mention-input[aria-busy="true"] .bb-comments-input-surface, +.bb-comments-mention-input:has(textarea[aria-disabled="true"]) + .bb-comments-input-surface { + background: color-mix( + in oklab, + var(--surface-recessed) 55%, + var(--background) + ); +} +.bb-comments-mention-input[aria-busy="true"] textarea, +.bb-comments-mention-input textarea[aria-disabled="true"] { + color: var(--muted-foreground); + cursor: wait; +} +.bb-comments-mention-input textarea:focus, +.bb-comments-mention-input textarea:focus-visible { + outline: none; + box-shadow: none; } .bb-comments-mention-input[data-mention-input-expanded="false"] { height: 30px; @@ -527,6 +583,8 @@ flex: 1; padding: 3px 9px 0; overflow: hidden; + border-radius: inherit; + transition: background-color 120ms ease; } .bb-comments-input-row { display: flex; diff --git a/plugins/timeline-comments/browser-check/harness.ts b/plugins/timeline-comments/browser-check/harness.ts index 81fd6fa..5f1c73b 100644 --- a/plugins/timeline-comments/browser-check/harness.ts +++ b/plugins/timeline-comments/browser-check/harness.ts @@ -63,6 +63,7 @@ const replies = Array.from({ length: 12 }, (_, index) => ({ })); const nativeFetch = window.fetch.bind(window); +let rejectNextReply = true; window.fetch = async (input, init) => { const url = String(input); const method = url.split("/").at(-1); @@ -78,6 +79,20 @@ window.fetch = async (input, init) => { nextCursor: null, }; } else if (method === "reply") { + await wait(260); + if (rejectNextReply) { + rejectNextReply = false; + return new Response( + JSON.stringify({ + ok: false, + error: { message: "Unable to save this comment." }, + }), + { + status: 500, + headers: { "content-type": "application/json" }, + }, + ); + } const insertedReply = { id: "comment_reply_incremental", threadId: summaries[0]!.id, @@ -222,7 +237,7 @@ void (async () => { throw new Error("Thread popover escaped the viewport"); if (document.activeElement !== popover) throw new Error("Thread popover did not receive focus"); - const reply = popover.querySelector( + let reply = popover.querySelector( ".bb-comments-reply-input", ); let replyButton = popover.querySelector( @@ -238,9 +253,21 @@ void (async () => { throw new Error("Reply input typography did not match BB normal text"); } const emptyReplyHeight = reply.getBoundingClientRect().height; - const replyComposer = reply.closest( + let replyComposer = reply.closest( ".bb-comments-mention-input", )!; + const restingBorderColor = getComputedStyle(replyComposer).borderColor; + reply.focus(); + await wait(150); + const focusedComposerStyle = getComputedStyle(replyComposer); + if ( + focusedComposerStyle.borderColor === restingBorderColor || + focusedComposerStyle.boxShadow === "none" + ) { + throw new Error( + `Focused reply did not use the BB ring treatment: resting=${restingBorderColor} focused=${focusedComposerStyle.borderColor} shadow=${focusedComposerStyle.boxShadow}`, + ); + } setTextareaValue(reply, "First line\nSecond line\nThird line"); await wait(30); if (replyComposer.getAnimations().length === 0) @@ -270,6 +297,14 @@ void (async () => { throw new Error("Valid reply did not enable submission"); if (replyComposer.dataset.mentionInputExpanded !== "true") throw new Error("Expanded reply layout did not stay latched like Moss"); + setTextareaValue(reply, "x".repeat(10_001)); + await wait(150); + if ( + replyComposer.querySelector(".bb-comments-error") === null || + getComputedStyle(replyComposer).borderColor === restingBorderColor + ) { + throw new Error("Invalid reply did not expose the BB destructive state"); + } setTextareaValue(reply, ""); await wait(30); if (replyComposer.getAnimations().length === 0) @@ -278,6 +313,31 @@ void (async () => { if (replyComposer.dataset.mentionInputExpanded !== "false") throw new Error("Cleared reply did not restore inline layout"); setTextareaValue(reply, "Ready"); + replyButton = popover.querySelector( + 'button[aria-label="Submit comment"]', + ); + replyButton?.click(); + await wait(150); + if ( + replyComposer.getAttribute("aria-busy") !== "true" || + !reply.readOnly || + getComputedStyle( + replyComposer.querySelector( + ".bb-comments-input-surface", + )!, + ).backgroundColor === "rgba(0, 0, 0, 0)" + ) { + throw new Error("Submitting reply did not expose its disabled BB state"); + } + await wait(140); + reply = popover.querySelector( + ".bb-comments-reply-input", + ); + replyComposer = reply.closest(".bb-comments-mention-input")!; + if (replyComposer.hasAttribute("aria-busy") || reply.readOnly) + throw new Error("Submitted reply did not restore the editable state"); + if (!popover.textContent?.includes("Unable to save this comment.")) + throw new Error("Failed reply did not expose the error state"); if (CSS.highlights.get("bb-timeline-comments")?.size !== 8) { throw new Error("Custom Highlight registry did not retain every anchor"); } diff --git a/plugins/timeline-comments/browser-check/run.mjs b/plugins/timeline-comments/browser-check/run.mjs index 2cf66ea..47e85fa 100644 --- a/plugins/timeline-comments/browser-check/run.mjs +++ b/plugins/timeline-comments/browser-check/run.mjs @@ -141,14 +141,23 @@ try { .locator('[data-comment-editing="true"] textarea') .evaluate((textarea) => { const style = getComputedStyle(textarea); + const composer = textarea.closest(".bb-comments-mention-input"); + const composerStyle = + composer instanceof HTMLElement ? getComputedStyle(composer) : null; return { active: document.activeElement === textarea, outlineStyle: style.outlineStyle, + composerBorderColor: composerStyle?.borderColor, + composerBoxShadow: composerStyle?.boxShadow, }; }); - if (!focusedEditInput.active || focusedEditInput.outlineStyle !== "auto") { + if ( + !focusedEditInput.active || + focusedEditInput.outlineStyle !== "none" || + focusedEditInput.composerBoxShadow === "none" + ) { throw new Error( - `Edit input did not keep the browser-native focus outline: ${JSON.stringify(focusedEditInput)}`, + `Edit input did not use the BB focus ring: ${JSON.stringify(focusedEditInput)}`, ); } await page.screenshot({ path: screenshot }); diff --git a/plugins/timeline-comments/dist/app.css b/plugins/timeline-comments/dist/app.css index 6bd1bef..d10a766 100644 --- a/plugins/timeline-comments/dist/app.css +++ b/plugins/timeline-comments/dist/app.css @@ -164,9 +164,13 @@ .underline { text-decoration-line: underline; } - .outline { - outline-style: var(--tw-outline-style); - outline-width: 1px; + .shadow { + --tw-shadow: var(--shadow); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .ring { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } .blur { --tw-blur: blur(8px); @@ -205,10 +209,70 @@ inherits: false; initial-value: solid; } -@property --tw-outline-style { +@property --tw-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-inset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-inset-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-inset-shadow-alpha { + syntax: ""; + inherits: false; + initial-value: 100%; +} +@property --tw-ring-color { + syntax: "*"; + inherits: false; +} +@property --tw-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-inset-ring-color { + syntax: "*"; + inherits: false; +} +@property --tw-inset-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-ring-inset { syntax: "*"; inherits: false; - initial-value: solid; +} +@property --tw-ring-offset-width { + syntax: ""; + inherits: false; + initial-value: 0px; +} +@property --tw-ring-offset-color { + syntax: "*"; + inherits: false; + initial-value: #fff; +} +@property --tw-ring-offset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; } @property --tw-blur { syntax: "*"; @@ -289,7 +353,20 @@ --tw-skew-x: initial; --tw-skew-y: initial; --tw-border-style: solid; - --tw-outline-style: solid; + --tw-shadow: 0 0 #0000; + --tw-shadow-color: initial; + --tw-shadow-alpha: 100%; + --tw-inset-shadow: 0 0 #0000; + --tw-inset-shadow-color: initial; + --tw-inset-shadow-alpha: 100%; + --tw-ring-color: initial; + --tw-ring-shadow: 0 0 #0000; + --tw-inset-ring-color: initial; + --tw-inset-ring-shadow: 0 0 #0000; + --tw-ring-inset: initial; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-offset-shadow: 0 0 #0000; --tw-blur: initial; --tw-brightness: initial; --tw-contrast: initial; @@ -440,7 +517,7 @@ box-sizing: border-box; padding: 5px 6px; overflow: visible; - transition: border-color 120ms ease; + transition: border-color 120ms ease, box-shadow 120ms ease; } .bb-comments-textarea, .bb-comments-reply-input, @@ -803,6 +880,52 @@ min-width: 0; flex: 1; flex-direction: column; + border: 1px solid var(--input, var(--border)); + border-radius: 8px; + background: var(--background); + box-shadow: 0 1px 2px color-mix(in oklab, var(--foreground) 8%, transparent); + transition: + border-color 120ms ease, + background-color 120ms ease, + box-shadow 120ms ease; +} +.bb-comments-composer .bb-comments-mention-input { + border: 0; + border-radius: 5px; + background: transparent; + box-shadow: none; +} +.bb-comments-mention-input:focus-within { + border-color: var(--ring); + box-shadow: 0 0 0 1px var(--ring); +} +.bb-comments-composer:focus-within { + border-color: var(--ring); + box-shadow: 0 0 0 1px var(--ring), 0 6px 18px color-mix(in oklab, var(--foreground) 12%, transparent); +} +.bb-comments-composer .bb-comments-mention-input:focus-within { + border-color: transparent; + box-shadow: none; +} +.bb-comments-mention-input:has(.bb-comments-error) { + border-color: var(--destructive-text, var(--foreground)); +} +.bb-comments-composer:has(.bb-comments-error) { + border-color: var(--destructive-text, var(--foreground)); +} +.bb-comments-mention-input[aria-busy=true] .bb-comments-input-surface, +.bb-comments-mention-input:has(textarea[aria-disabled=true]) .bb-comments-input-surface { + background: color-mix(in oklab, var(--surface-recessed) 55%, var(--background)); +} +.bb-comments-mention-input[aria-busy=true] textarea, +.bb-comments-mention-input textarea[aria-disabled=true] { + color: var(--muted-foreground); + cursor: wait; +} +.bb-comments-mention-input textarea:focus, +.bb-comments-mention-input textarea:focus-visible { + outline: none; + box-shadow: none; } .bb-comments-mention-input[data-mention-input-expanded=false] { height: 30px; @@ -813,6 +936,8 @@ flex: 1; padding: 3px 9px 0; overflow: hidden; + border-radius: inherit; + transition: background-color 120ms ease; } .bb-comments-input-row { display: flex;