diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2bca28a2..7f0c72bb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -272,6 +272,18 @@ jobs:
find tests/e2e infra/scripts -type f -name '*.sh' -print0 \
| xargs -0 shellcheck --severity=warning
+ - if: needs.changes.outputs.scripts == 'true'
+ name: Run E2E bash unit tests (no live infra)
+ # Pure-bash unit tests for E2E helper libs (lib/*.sh). These pin
+ # behavior of dispatch logic that — when broken — silently masks as
+ # "Could not resolve authentication method" only after a successful
+ # tenant + workspace provision (PR #2571 incident, 2026-05-03). Add
+ # new self-contained unit tests here as the lib/ directory grows;
+ # tests requiring live CP/tenant credentials belong in the dedicated
+ # e2e-staging-* workflows, not this job.
+ run: |
+ bash tests/e2e/test_model_slug.sh
+
canvas-deploy-reminder:
name: Canvas Deploy Reminder
runs-on: ubuntu-latest
diff --git a/canvas/src/app/globals.css b/canvas/src/app/globals.css
index 5fa20e9b..71013ed1 100644
--- a/canvas/src/app/globals.css
+++ b/canvas/src/app/globals.css
@@ -1,6 +1,15 @@
@import "tailwindcss";
@plugin "@tailwindcss/typography";
+/*
+ * Tailwind v4 defaults the `dark:` variant to `prefers-color-scheme: dark`.
+ * Our theme switcher writes `data-theme="dark"` on instead (so user
+ * choice via the toggle wins over OS preference). Re-bind `dark:` to that
+ * attribute so component classes like `dark:bg-zinc-800` track the same
+ * source of truth as the `[data-theme="dark"]` token overrides below.
+ */
+@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
+
/*
* Load order:
* 1. Tailwind core (v4) — provides preflight + utility generation.
diff --git a/canvas/src/components/tabs/ChatTab.tsx b/canvas/src/components/tabs/ChatTab.tsx
index 6c2c57cb..a218192e 100644
--- a/canvas/src/components/tabs/ChatTab.tsx
+++ b/canvas/src/components/tabs/ChatTab.tsx
@@ -177,10 +177,10 @@ export function ChatTab({ workspaceId, data }: Props) {
aria-controls="chat-panel-my-chat"
tabIndex={subTab === "my-chat" ? 0 : -1}
onClick={() => setSubTab("my-chat")}
- className={`px-3 py-1.5 text-[10px] font-medium transition-colors ${
+ className={`px-3 py-1.5 text-[10px] font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ${
subTab === "my-chat"
? "text-ink border-b-2 border-accent"
- : "text-ink-soft hover:text-ink-mid"
+ : "text-ink-mid hover:text-ink"
}`}
>
My Chat
@@ -192,10 +192,10 @@ export function ChatTab({ workspaceId, data }: Props) {
aria-controls="chat-panel-agent-comms"
tabIndex={subTab === "agent-comms" ? 0 : -1}
onClick={() => setSubTab("agent-comms")}
- className={`px-3 py-1.5 text-[10px] font-medium transition-colors ${
+ className={`px-3 py-1.5 text-[10px] font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ${
subTab === "agent-comms"
? "text-ink border-b-2 border-accent"
- : "text-ink-soft hover:text-ink-mid"
+ : "text-ink-mid hover:text-ink"
}`}
>
Agent Comms
@@ -773,10 +773,22 @@ function MyChatPanel({ workspaceId, data }: Props) {
{msg.content && (
@@ -896,7 +908,7 @@ function MyChatPanel({ workspaceId, data }: Props) {
placeholder={agentReachable ? "Send a message... (Shift+Enter for new line, paste images to attach)" : `Agent is ${data.status}`}
disabled={!agentReachable || sending}
rows={1}
- className="flex-1 bg-surface-card border border-line rounded-lg px-3 py-2 text-xs text-ink placeholder-zinc-500 focus:outline-none focus:border-accent resize-none disabled:opacity-50"
+ className="flex-1 bg-surface-card border border-line rounded-lg px-3 py-2 text-xs text-ink placeholder-ink-soft dark:bg-zinc-800 dark:border-zinc-600 dark:placeholder-zinc-500 focus:outline-none focus:border-accent focus-visible:ring-2 focus-visible:ring-accent/40 resize-none disabled:opacity-50"
/>