Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
edcfd0c
test(widget): file the install-status success path under its own cont…
MKindshofer Sep 14, 2026
edc681b
feat(ui): migrate overlay primitives from Radix to Base UI
mortondev Sep 11, 2026
11ff989
fix(inbox): keep composer focus across send
mortondev Sep 11, 2026
163dc66
perf(editor): cut per-keystroke work in post and comment composers
mortondev Sep 11, 2026
b065bc1
fix(pr-545): address Codex review feedback
mortondev Sep 11, 2026
d8e82ab
fix(pr-545): resolve follow-up review threads
mortondev Sep 11, 2026
07d3fb9
fix(pr-545): resolve second review round
mortondev Sep 11, 2026
71b3f82
fix(billing): give the branding checkbox its own label path
mortondev Sep 11, 2026
285168b
refactor: simplification batch, dead-code sweep and shared building b…
mortondev Sep 11, 2026
3f842fb
refactor: share taxonomy validators, ticket slugs, and attribute fn s…
mortondev Sep 11, 2026
af76433
refactor: share nextPosition, attribute types, and webhook safe-dispatch
mortondev Sep 11, 2026
9318eab
chore(upstream): follow the Base UI and simplification picks through …
MKindshofer Sep 14, 2026
6588ed0
test(ui): make the fork's Base UI interaction suites deterministic
MKindshofer Sep 14, 2026
d035d23
test(upstream): pin the batch C picks against their contract
MKindshofer Sep 15, 2026
85853c0
fix(mutation-gate): keep Stryker's sandbox copies byte-identical to t…
MKindshofer Sep 15, 2026
8dde600
test(upstream): close the batch C mutation holes and pin the confirme…
MKindshofer Sep 15, 2026
f9098b3
test(upstream): pin C5 and G14, and kill the last emoji ranking survi…
MKindshofer Sep 15, 2026
0246782
test(mutation-gate): excuse the six emoji ranking mutants no test can…
MKindshofer Sep 15, 2026
e3d208a
docs(self-improve): record the sharded coverage run and the shared mu…
MKindshofer Sep 15, 2026
ce003df
ci(mutation): say what the budget headroom was measured against
MKindshofer Sep 15, 2026
dbf789a
docs(self-improve): a manifest entry can name a subset of a file's su…
MKindshofer Sep 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ jobs:
- run: bun run --cwd packages/widget typecheck
- run: bun run --cwd packages/widget test
- run: bun run --cwd packages/db typecheck
# `packages/ids` has a typecheck script and nothing ran it, so the id
# helpers and their suite were the one workspace TypeScript no job
# compiled — found by a cast that only `tsc` rejects.
- run: bun run --cwd packages/ids typecheck
- run: bunx tsc --noEmit -p packages/email/tsconfig.json
# `scripts/` was the only TypeScript nothing typechecked, and the
# dependency-audit gate lives there.
Expand Down Expand Up @@ -174,7 +178,7 @@ jobs:
# grades tests that pass.
needs: unit
runs-on: ubuntu-latest
timeout-minutes: 20
timeout-minutes: 30
env:
# Without a database the DB-backed suites skip themselves, Stryker sees no
# test covering the code they hold, and the gate reports those mutants as
Expand Down Expand Up @@ -208,7 +212,13 @@ jobs:
- name: Every mutant of the touched code has to be caught
env:
DIFF_BASE: origin/${{ github.event.pull_request.base.ref || github.event.repository.default_branch }}
MUTATION_BUDGET_SECONDS: '900'
# Wall clock for the whole run, and an exceeded budget fails as
# "nothing measured" rather than as a low score. 900 was sized when
# the gate graded 15 files and 714 mutants; the manifest now reaches
# 16 and 1,037. Measured on both machines afterwards: 8m30s on a
# developer laptop and under 6m on the runner, so the headroom is for
# the next few manifest entries rather than for this batch.
MUTATION_BUDGET_SECONDS: '1500'
run: bun scripts/mutation-check.ts

e2e-smoke:
Expand Down
342 changes: 284 additions & 58 deletions SELF-IMPROVE.md

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions UPSTREAM.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,16 @@ the deploy tag where there is one.
| `06e7de186` #542 | 2026-09-11 | fix(e2e): bust caches with IN and retarget post-MCP-move specs | picked | #32, `v0.13.3-exkulpa.29` |
| `8b38983dc` #543 | 2026-09-11 | test(mcp): fail if a resource ships without a 403 mapping | picked | #32, `v0.13.3-exkulpa.29` |
| `98b18e3ee` | 2026-09-11 | feat(widget): pair the agent install so the signing secret never hits chat | picked | #32, `v0.13.3-exkulpa.29` |
| `0935071a6` #545 | 2026-09-11 | feat(ui): migrate overlay primitives from Radix to Base UI | planned, batch C | — |
| `ee44b03ba` #545 | 2026-09-11 | fix(inbox): keep composer focus across send | planned, batch C | — |
| `ebe630799` #545 | 2026-09-11 | perf(editor): cut per-keystroke work in post and comment composers | planned, batch C | — |
| `94dcab894` #545 | 2026-09-11 | fix(pr-545): address Codex review feedback | planned, batch C | — |
| `48045ccc3` #545 | 2026-09-11 | fix(pr-545): resolve follow-up review threads | planned, batch C | — |
| `c926b53f3` #545 | 2026-09-11 | fix(pr-545): resolve second review round | planned, batch C | — |
| `9a92f530e` #545 | 2026-09-11 | fix(billing): give the branding checkbox its own label path | planned, batch C (the checkbox primitive, not billing) | — |
| `4aa245e63` #546 | 2026-09-11 | refactor: simplification batch, dead-code sweep and shared building blocks | planned, batch C | — |
| `e31eb058b` #546 | 2026-09-11 | refactor: share taxonomy validators, ticket slugs, and attribute fn schemas | planned, batch C | — |
| `0c9255422` #546 | 2026-09-11 | refactor: share nextPosition, attribute types, and webhook safe-dispatch | planned, batch C | — |
| `0935071a6` #545 | 2026-09-11 | feat(ui): migrate overlay primitives from Radix to Base UI | picked | #33, `v0.13.3-exkulpa.30` |
| `ee44b03ba` #545 | 2026-09-11 | fix(inbox): keep composer focus across send | picked | #33, `v0.13.3-exkulpa.30` |
| `ebe630799` #545 | 2026-09-11 | perf(editor): cut per-keystroke work in post and comment composers | picked | #33, `v0.13.3-exkulpa.30` |
| `94dcab894` #545 | 2026-09-11 | fix(pr-545): address Codex review feedback | picked | #33, `v0.13.3-exkulpa.30` |
| `48045ccc3` #545 | 2026-09-11 | fix(pr-545): resolve follow-up review threads | picked | #33, `v0.13.3-exkulpa.30` |
| `c926b53f3` #545 | 2026-09-11 | fix(pr-545): resolve second review round | picked | #33, `v0.13.3-exkulpa.30` |
| `9a92f530e` #545 | 2026-09-11 | fix(billing): give the branding checkbox its own label path | picked (the checkbox primitive, not billing) | #33, `v0.13.3-exkulpa.30` |
| `4aa245e63` #546 | 2026-09-11 | refactor: simplification batch, dead-code sweep and shared building blocks | picked | #33, `v0.13.3-exkulpa.30` |
| `e31eb058b` #546 | 2026-09-11 | refactor: share taxonomy validators, ticket slugs, and attribute fn schemas | picked | #33, `v0.13.3-exkulpa.30` |
| `0c9255422` #546 | 2026-09-11 | refactor: share nextPosition, attribute types, and webhook safe-dispatch | picked | #33, `v0.13.3-exkulpa.30` |
| `99fb7a1d2` #547 | 2026-09-12 | feat(widget): scope sessions by audience (migration 0280) | planned, batch D | — |
| `5cd9c0c11` #550 | 2026-09-12 | fix(auth): pre-insert MCP oauth_resource before Better Auth seed | picked (needed with 1.7.4) | #32, `v0.13.3-exkulpa.29` |
| `74b480a2a` #551 | 2026-09-12 | fix(auth): keep OIDC sign-out local to Quackback | picked (needed with 1.7.4; Zitadel logout) | #32, `v0.13.3-exkulpa.29` |
Expand Down
202 changes: 202 additions & 0 deletions apps/web/e2e/tests/admin/base-ui-components.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import { expect, test, type Page } from '@playwright/test'
import { waitForHydration } from '../../utils/helpers'

/**
* Site-wide usability pass for the Radix → Base UI migration.
* Opens each primitive on a real admin page the way an admin would, then
* checks the overlay is usable (visible, labelled, dismissible).
*/
async function waitForAdmin(page: Page) {
await expect(page.getByRole('navigation').first()).toBeVisible({ timeout: 15_000 })
}

test.describe('Base UI component usability', () => {
test.use({ viewport: { width: 1920, height: 1080 } })
test.describe.configure({ timeout: 60_000 })

test('tooltip, dropdown, and popover open and dismiss', async ({ page }) => {
await page.goto('/admin/feedback')
await waitForAdmin(page)

const switcher = page.getByRole('button', { name: 'Switch workspace' })
if ((await switcher.count()) > 0) {
await waitForHydration(switcher)
await switcher.hover()
await expect(page.locator('[data-slot="tooltip-content"]').first()).toBeVisible({
timeout: 5_000,
})
await switcher.click()
await expect(page.getByRole('menu')).toBeVisible()
await expect(page.getByRole('menuitem').first()).toBeVisible()
await page.keyboard.press('Escape')
await expect(page.getByRole('menu')).toHaveCount(0)
}

const bell = page.getByRole('button', { name: /notifications/i })
await waitForHydration(bell)
await bell.click()
await expect(page.locator('[data-slot="popover-content"]')).toBeVisible()
await page.keyboard.press('Escape')
await expect(page.locator('[data-slot="popover-content"]')).toHaveCount(0)
})

test('switch, select, slider, and styled links stay usable', async ({ page }) => {
await page.goto('/admin/settings/general')
await expect(page.getByRole('heading', { name: 'General' })).toBeVisible({ timeout: 15_000 })
const productSwitch = page.locator('#product-changelog')
await waitForHydration(productSwitch)
const checked = await productSwitch.getAttribute('aria-checked')
expect(checked === 'true' || checked === 'false').toBeTruthy()

await page.goto('/admin/settings/widget')
await expect(page.getByRole('heading', { name: 'Widget' }).first()).toBeVisible({
timeout: 15_000,
})
const position = page.locator('#widget-position')
await waitForHydration(position)
const shown = await position.innerText()
await position.click()
const listbox = page.locator('[data-slot="select-content"], [role="listbox"]').first()
await expect(listbox).toBeVisible()
await expect(page.getByRole('option').first()).toBeVisible()
await page.keyboard.press('Escape')
await expect(listbox).toBeHidden()
await expect(position).toHaveText(shown)

const install = page.getByRole('link', { name: /Install widget|View installation/ })
await expect(install).toBeVisible()
await expect(install).not.toHaveAttribute('role', 'button')

await page.goto('/admin/settings/portal')
await expect(page.getByRole('heading', { name: 'Portal' })).toBeVisible({ timeout: 15_000 })
const slider = page.getByRole('slider')
await expect(slider).toBeVisible()
await expect(slider).toHaveAttribute('aria-valuenow')
})

test('tabs, dialog, radio, and checkbox stay usable', async ({ page }) => {
await page.goto('/admin/settings/boards')
const newBoard = page.getByRole('button', { name: 'New board' })
await waitForHydration(newBoard)
await newBoard.click()
const createDialog = page.getByRole('dialog')
await expect(createDialog).toBeVisible()
await expect(createDialog.getByLabel('Board name')).toBeVisible()
await page.keyboard.press('Escape')
await expect(createDialog).toBeHidden()

const firstBoard = page.locator('a[href*="/admin/settings/boards/"]').first()
await expect(firstBoard).toBeVisible()
await firstBoard.click()
const general = page.getByRole('tab', { name: 'General' })
const access = page.getByRole('tab', { name: 'Access' })
await waitForHydration(general)
await expect(general).toHaveAttribute('data-active')
await access.click()
await expect(access).toHaveAttribute('data-active')
await expect(general).not.toHaveAttribute('data-active')

await page.goto('/admin/settings/tags')
const addTag = page.getByText('Add new tag')
await waitForHydration(addTag)
await addTag.click()
const tagDialog = page.getByRole('dialog')
await expect(tagDialog).toBeVisible()
const portal = tagDialog.getByRole('radio', { name: /^portal$/i })
const internal = tagDialog.getByRole('radio', { name: /^internal$/i })
await expect(portal).toHaveAttribute('aria-checked', 'true')
await internal.click()
await expect(internal).toHaveAttribute('aria-checked', 'true')
await tagDialog.getByRole('button', { name: /cancel/i }).click()
await expect(tagDialog).toBeHidden()

await page.goto('/admin/settings/office-hours')
await expect(page.getByRole('heading', { name: 'Office Hours' })).toBeVisible({
timeout: 15_000,
})
await expect(page.locator('#office-hours-enabled')).toBeVisible()
if ((await page.getByRole('checkbox').count()) > 0) {
await expect(page.getByRole('checkbox').first()).toBeEnabled()
}
})

test('menu checkbox items, alert dialog, and dropdown stay usable', async ({ page }) => {
await page.goto('/admin/users')
await expect(page.getByPlaceholder('Search users...')).toBeVisible({ timeout: 15_000 })
const columns = page.getByRole('button', { name: 'Columns' })
await waitForHydration(columns)
await columns.click()
const checkboxItem = page.getByRole('menuitemcheckbox').first()
await expect(checkboxItem).toBeVisible()
const before = await checkboxItem.getAttribute('aria-checked')
await checkboxItem.click()
await expect(checkboxItem).not.toHaveAttribute('aria-checked', before ?? '')
await checkboxItem.click()
await expect(checkboxItem).toHaveAttribute('aria-checked', before ?? '')
await page.keyboard.press('Escape')

await page.goto('/admin/notifications')
await expect(page.getByRole('heading', { name: /notification/i }).first()).toBeVisible({
timeout: 15_000,
})
const allTab = page.getByRole('tab', { name: 'All' })
const unreadTab = page.getByRole('tab', { name: /unread/i })
if ((await allTab.count()) > 0) {
await waitForHydration(unreadTab)
await unreadTab.click()
await expect(unreadTab).toHaveAttribute('data-active')
await allTab.click()
await expect(allTab).toHaveAttribute('data-active')
}
const moreActions = page.getByRole('button', { name: 'More notification actions' })
await waitForHydration(moreActions)
await moreActions.click()
await page.getByRole('menuitem', { name: 'Archive all read' }).click()
const alert = page.getByRole('alertdialog')
await expect(alert).toBeVisible()
await expect(alert.getByText('Archive all read notifications?')).toBeVisible()
await alert.getByRole('button', { name: 'Cancel' }).click()
await expect(alert).toBeHidden()
})

test('inbox tabs and collapsible work on a conversation', async ({ page }) => {
await page.goto('/admin/inbox')
await waitForAdmin(page)
const row = page.locator('a[href*="inbox"], [data-testid="inbox-row"]').first()
const textRow = page.getByRole('main').getByRole('button').first()
const target = (await row.count()) > 0 ? row : textRow
if ((await target.count()) === 0) return

await target.click({ timeout: 8_000 }).catch(() => {})
const details = page.getByRole('tab', { name: 'Details' })
if ((await details.count()) === 0) return

await expect(details).toBeVisible()
await waitForHydration(details)
const copilot = page.getByRole('tab', { name: /copilot/i })
if ((await copilot.count()) > 0) {
await waitForHydration(copilot)
await copilot.click()
await expect(copilot).toHaveAttribute('data-active')
await details.click()
await expect(details).toHaveAttribute('data-active')
}

const collapse = page.getByRole('button', { name: /properties|activity|similar/i }).first()
if ((await collapse.count()) > 0) {
await collapse.click()
}
})

test('sheet opens on a phone viewport', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 })
await page.goto('/admin/feedback')
const menu = page.getByRole('button', { name: 'Open menu' })
await waitForHydration(menu)
await menu.click()
const sheet = page.locator('[data-slot="sheet-content"]').or(page.getByRole('dialog')).first()
await expect(sheet).toBeVisible()
await page.keyboard.press('Escape')
await expect(sheet).toBeHidden()
})
})
11 changes: 4 additions & 7 deletions apps/web/e2e/tests/admin/boards.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ test.describe('Admin Board Management', () => {

test('can access board general settings', async ({ page }) => {
await openFirstBoard(page)
await expect(page.getByRole('tab', { name: 'General' })).toHaveAttribute('data-state', 'active')
await expect(page.getByRole('tab', { name: 'General' })).toHaveAttribute('data-active')
await expect(page.getByRole('textbox', { name: 'Board name', exact: true })).toBeVisible()
await expect(page.getByRole('navigation', { name: 'Breadcrumb' })).toBeVisible()
await expect(page.getByTestId('board-switcher')).toHaveCount(0)
Expand Down Expand Up @@ -627,7 +627,7 @@ test.describe('Board Settings Tabs', () => {
const generalTab = page.getByRole('tab', { name: 'General' })
if ((await generalTab.count()) === 0) return

await expect(generalTab).toHaveAttribute('data-state', 'active')
await expect(generalTab).toHaveAttribute('data-active')
})

test('active tab button is visually distinct after switching', async ({ page }) => {
Expand All @@ -637,11 +637,8 @@ test.describe('Board Settings Tabs', () => {
await accessTab.click()
await page.waitForLoadState('networkidle')

await expect(accessTab).toHaveAttribute('data-state', 'active')
await expect(page.getByRole('tab', { name: 'General' })).toHaveAttribute(
'data-state',
'inactive'
)
await expect(accessTab).toHaveAttribute('data-active')
await expect(page.getByRole('tab', { name: 'General' })).not.toHaveAttribute('data-active')
})
})

Expand Down
Loading
Loading