Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Repository structure:
- In Playwright tests, prefer accessible selectors first: `getByRole`, `getByLabel`, `getByText`, and explicit accessible names.
- Avoid `locator()` for interactive controls when a semantic selector is available.
- Use `locator()` only as a fallback for cases without reliable semantics (for example: document root `html`, structural class assertions, or implementation-only hooks).
- For known WebKit HTML `<dialog>` top-layer issues, prefer a stable dialog id locator and `evaluate`-based click for dialog confirmation controls.
- When testability needs improvement, prefer adding accessibility semantics (`role`, `aria-label`, `aria-labelledby`) over introducing new id-only selectors.

## CDN and runtime expectations
Expand Down
9 changes: 5 additions & 4 deletions docs/localstorage-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,17 @@ This document is the source of truth for what `@knighted/develop` stores in `loc

1. `knighted:develop:github-pat`
- GitHub personal access token used for API calls.
2. `knighted:develop:github-repository`
- Last selected repository full name (for example: `owner/repo`).
3. `knighted-develop:render-mode`
2. `knighted-develop:render-mode`
- Last selected render mode (`dom` or `react`).
4. Theme/UI preference keys managed by layout theme modules.
3. Theme/UI preference keys managed by layout theme modules.

## Not Allowed In localStorage

Do not store pull request context in `localStorage`.

Examples that must stay out of `localStorage`:

- Selected repository preference (`owner/repo`)
- PR context state (`active`, `disconnected`, `closed`, `inactive`)
- PR number and URL
- PR base/head/title/body
Expand All @@ -31,3 +30,5 @@ Examples that must stay out of `localStorage`:
`localStorage` is for lightweight bootstrap preferences only.

If data is needed to restore workspace or pull request workflow state, it belongs in IndexedDB workspace records.

Repository selection is derived from in-memory BYOT controls and IndexedDB-backed workspace records, not from a dedicated localStorage key.
17 changes: 17 additions & 0 deletions docs/playwright-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Playwright Testing Notes

## WebKit and HTML Dialog Overlays

WebKit can be sensitive to Playwright actionability checks when interacting with
HTML `<dialog>` overlays. In some flows, role-based or standard click actions can
time out even when controls are visibly rendered and usable.

Use this fallback pattern for dialog confirmation flows when WebKit flakes:

1. Target the dialog by stable id (for example: `#clear-confirm-dialog`) instead
of a broad `getByRole('dialog')` selector.
2. Use `evaluate`-based click for submit/confirm controls inside the dialog.
3. Scope text assertions to the dialog locator to avoid matching background UI.

Keep accessible selectors as the default in tests. Use this dialog fallback only
for known WebKit top-layer interaction issues.
19 changes: 11 additions & 8 deletions docs/pr-context-storage-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,22 @@ Use this matrix as the source of truth when debugging UI/storage mismatch.

## Current Workspace Selection On Load

When the app loads or the selected repository changes, the app selects a workspace from IndexedDB using repository-scoped records only.
When the app loads, workspace restore scope depends on whether a repository is selected.

- If a repository is selected: use repository-scoped records only (`repo` match).
- If no repository is selected: evaluate all stored workspace records.

Selection order:

1. Load records for the currently selected repository (`repo` match).
2. Compute a preferred id from in-memory state:
1. Load candidate records using the scope above.
2. Compute preferred candidates from in-memory state:

- Existing in-memory active record id when available.
- Otherwise canonical id derived from current repository + head.
- Preferred by id: existing in-memory active record id when available.
- Preferred by workspace key: current repository + head (`workspaceKey`).

3. If the preferred record exists and is `active`, select it.
4. Otherwise select the first `active` record in that repository.
5. Otherwise select the preferred record if present.
3. If preferred-by-id or preferred-by-key exists and is `active`, select it.
4. Otherwise select the first `active` record in candidates.
5. Otherwise select preferred-by-id or preferred-by-key if present.
6. Otherwise fall back to the first record returned by IDB ordering.

Notes:
Expand Down
17 changes: 7 additions & 10 deletions playwright/diagnostics.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
runTypecheck,
setComponentEditorSource,
setStylesEditorSource,
waitForLintDiagnosticsIssues,
waitForInitialRender,
} from './helpers/app-test-helpers.js'

Expand Down Expand Up @@ -337,9 +338,7 @@ test('component lint reports missing button type prop', async ({ page }) => {

await runComponentLint(page)

await expect(page.getByText(/Rendered \(Lint issues: [1-9]\d*\)/)).toBeVisible()
await ensureDiagnosticsDrawerOpen(page)
await expect(page.getByText('Biome reported issues.')).toBeVisible()
await waitForLintDiagnosticsIssues(page)
await expect(page.getByText(/a11y\/useButtonType/)).toBeVisible()
})

Expand All @@ -354,10 +353,7 @@ test('styles diagnostics rows navigate editor to reported line', async ({ page }

await runStylesLint(page)

await expect(page.getByRole('button', { name: /^Diagnostics/ })).toHaveClass(
/diagnostics-toggle--error/,
)
await ensureDiagnosticsDrawerOpen(page)
await waitForLintDiagnosticsIssues(page)

const targetDiagnostic = page.getByRole('button', { name: /^L3(:\d+)?\s/ }).first()
await expect(targetDiagnostic).toBeVisible()
Expand All @@ -375,9 +371,10 @@ test('styles lint reports CSS syntax errors', async ({ page }) => {

await runStylesLint(page)

await expect(page.getByText(/Rendered \(Lint issues: [1-9]\d*\)/)).toBeVisible()
await ensureDiagnosticsDrawerOpen(page)
await expect(page.getByText('Biome reported issues.')).toBeVisible()
await waitForLintDiagnosticsIssues(page)
await expect(page.locator('#diagnostics-styles')).toContainText(
'Biome reported issues.',
)
})

test('sass compiler warnings surface in styles diagnostics', async ({ page }) => {
Expand Down
26 changes: 22 additions & 4 deletions playwright/github-byot-ai.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -760,20 +760,37 @@
.fill('github_pat_fake_1234567890')
await page.getByRole('button', { name: 'Add GitHub token' }).click()

await ensureOpenPrDrawerOpen(page)

const repoSelect = page.getByLabel('Pull request repository')
await expect(repoSelect).toBeEnabled()
await expect(repoSelect).toBeDisabled()
await expect(page.getByRole('status', { name: 'App status' })).toHaveText(
'Loaded 2 writable repositories',
)

await repoSelect.selectOption('knightedcodemonkey/develop')
await page.getByRole('button', { name: 'Workspaces' }).click()
const workspaceRepositoryFilter = page.getByLabel('Workspace repository filter')
const storedContextsSelect = page.getByLabel('Stored local editor contexts')
const openStoredContextButton = page.getByRole('button', {
name: 'Open',
exact: true,
})
await expect(workspaceRepositoryFilter).toBeVisible()
await workspaceRepositoryFilter.selectOption('knightedcodemonkey/develop')
await expect(workspaceRepositoryFilter).toHaveValue('knightedcodemonkey/develop')

await expect(storedContextsSelect).toBeVisible()
await storedContextsSelect.selectOption({
label: 'Start new context for knightedcodemonkey/develop',
})
await expect(openStoredContextButton).toBeEnabled()
await openStoredContextButton.click()
await page.getByRole('button', { name: 'Close workspaces drawer' }).click()

await ensureOpenPrDrawerOpen(page)
await expect(repoSelect).toHaveValue('knightedcodemonkey/develop')

await page.reload()
await expect(page.getByRole('heading', { name: '@knighted/develop' })).toBeVisible()
await expect(page.getByRole('status', { name: 'App status' })).toHaveText(

Check failure on line 793 in playwright/github-byot-ai.spec.ts

View workflow job for this annotation

GitHub Actions / E2E (Playwright, chromium)

[chromium] › playwright/github-byot-ai.spec.ts:723:1 › BYOT remembers selected repository across reloads

1) [chromium] › playwright/github-byot-ai.spec.ts:723:1 › BYOT remembers selected repository across reloads Error: expect(locator).toHaveText(expected) failed Locator: getByRole('status', { name: 'App status' }) Expected: "Loaded 2 writable repositories" Received: "Rendered" Timeout: 60000ms Call log: - Expect "toHaveText" with timeout 60000ms - waiting for getByRole('status', { name: 'App status' }) 3 × locator resolved to <div id="status" role="status" class="status" aria-label="App status">Idle</div> - unexpected value "Idle" - locator resolved to <div id="status" role="status" aria-label="App status" class="status status--pending">Loading writable repositories from GitHub...</div> - unexpected value "Loading writable repositories from GitHub..." - locator resolved to <div id="status" role="status" aria-label="App status" class="status status--pending">Loading CDN assets…</div> - unexpected value "Loading CDN assets…" 58 × locator resolved to <div id="status" role="status" aria-label="App status" class="status status--neutral">Rendered</div> - unexpected value "Rendered" 791 | await page.reload() 792 | await expect(page.getByRole('heading', { name: '@knighted/develop' })).toBeVisible() > 793 | await expect(page.getByRole('status', { name: 'App status' })).toHaveText( | ^ 794 | 'Loaded 2 writable repositories', 795 | { 796 | timeout: 60_000, at /home/runner/work/develop/develop/playwright/github-byot-ai.spec.ts:793:66
'Loaded 2 writable repositories',
{
timeout: 60_000,
Expand All @@ -783,4 +800,5 @@
await expect(page.getByRole('button', { name: 'Delete GitHub token' })).toBeVisible()
await ensureOpenPrDrawerOpen(page)
await expect(repoSelect).toHaveValue('knightedcodemonkey/develop')
await expect(repoSelect).toBeDisabled()
})
Loading
Loading