From 8c7b6b2e6639b747582fafc61652e3c03dcb288e Mon Sep 17 00:00:00 2001 From: KCM Date: Thu, 7 May 2026 21:40:33 -0500 Subject: [PATCH 1/4] feat: typography workspace selection. --- playwright/rendering-modes/core.spec.ts | 47 +++++++ src/app.js | 40 ++++++ src/index.html | 20 +++ src/modules/app-core/app-bindings-startup.js | 2 + .../app-core/app-composition-options.js | 5 + .../app-core/github-workflows-setup.js | 1 + src/modules/app-core/github-workflows.js | 27 ++++ .../app-core/workspace-context-controller.js | 18 +++ .../app-core/workspace-controllers-setup.js | 6 + .../app-core/workspace-sync-controller.js | 3 + .../iframe-preview-executor.js | 63 ++++++++- src/modules/preview/preview-font.js | 131 ++++++++++++++++++ src/modules/preview/render-runtime.js | 37 +++++ src/modules/workspace/workspace-storage.js | 76 +++++++++- .../workspace/workspaces-drawer/drawer.js | 39 ++++++ src/styles/ai-controls.css | 19 +++ 16 files changed, 532 insertions(+), 2 deletions(-) create mode 100644 src/modules/preview/preview-font.js diff --git a/playwright/rendering-modes/core.spec.ts b/playwright/rendering-modes/core.spec.ts index 4a28a12..dd4f191 100644 --- a/playwright/rendering-modes/core.spec.ts +++ b/playwright/rendering-modes/core.spec.ts @@ -128,6 +128,7 @@ const readLatestWorkspaceSnapshot = async (page: import('@playwright/test').Page return { renderMode: typeof latest.renderMode === 'string' ? latest.renderMode : '', + fontCssUrl: typeof latest.fontCssUrl === 'string' ? latest.fontCssUrl : '', styleLanguage: typeof primaryStylesTab?.language === 'string' ? primaryStylesTab.language : '', } @@ -147,6 +148,12 @@ const readPreviewUserStyleText = async (page: import('@playwright/test').Page) = }) } +const readPreviewBodyFontFamily = async (page: import('@playwright/test').Page) => { + return getPreviewFrame(page) + .locator('html') + .evaluate(() => getComputedStyle(document.body).fontFamily || '') +} + test.beforeEach(async ({ page }) => { await resetWorkbenchStorage(page) }) @@ -228,6 +235,46 @@ test('reactJsx tag interpolation renders memo and forwardRef components', async .toBe(0) }) +test('workspace font CSS URL applies to preview and persists per workspace', async ({ + page, +}) => { + await waitForInitialRender(page) + + await page.getByRole('button', { name: 'Workspaces' }).click() + + const fontCssUrlInput = page.getByRole('textbox', { + name: 'Workspace Font CSS URL', + }) + await fontCssUrlInput.fill( + 'https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;700&display=swap', + ) + await fontCssUrlInput.press('Tab') + + await expect + .poll(async () => (await readPreviewBodyFontFamily(page)).toLowerCase()) + .toContain('ibm plex sans') + + await expect + .poll(async () => { + const snapshot = await readLatestWorkspaceSnapshot(page) + return snapshot?.fontCssUrl ?? '' + }) + .toBe( + 'https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;700&display=swap', + ) + + await page.reload() + await waitForInitialRender(page) + await page.getByRole('button', { name: 'Workspaces' }).click() + + await expect(fontCssUrlInput).toHaveValue( + 'https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;700&display=swap', + ) + await expect + .poll(async () => (await readPreviewBodyFontFamily(page)).toLowerCase()) + .toContain('ibm plex sans') +}) + test('react mode keeps App.ts entry but surfaces rename guidance until compatible', async ({ page, }) => { diff --git a/src/app.js b/src/app.js index 5bdd8bb..e6118c0 100644 --- a/src/app.js +++ b/src/app.js @@ -66,6 +66,11 @@ import { createGitHubPrDrawer } from './modules/github/pr/drawer/controller/crea import { createLayoutThemeController } from './modules/ui/layout-theme.js' import { createLintDiagnosticsController } from './modules/diagnostics/lint-diagnostics.js' import { createPreviewBackgroundController } from './modules/preview/preview-background.js' +import { + createPreviewFontController, + defaultPreviewFontCssUrl, + normalizePreviewFontCssUrl, +} from './modules/preview/preview-font.js' import { getReactEntryTabCompatibilityError } from './modules/preview/preview-entry-resolver.js' import { createRenderRuntimeController } from './modules/preview/render-runtime.js' import { createTypeDiagnosticsController } from './modules/diagnostics/type-diagnostics.js' @@ -151,6 +156,8 @@ const workspacesInitialize = document.getElementById('workspaces-initialize') const workspacesShare = document.getElementById('workspaces-share') const workspacesNew = document.getElementById('workspaces-new') const workspacesSelect = document.getElementById('workspaces-select') +const workspacesFontCssUrlInput = document.getElementById('workspaces-font-css-url') +const workspacesFontCssUrlLoad = document.getElementById('workspaces-font-css-url-load') const workspacesOpen = document.getElementById('workspaces-open') const workspacesRename = document.getElementById('workspaces-rename') const workspacesRemove = document.getElementById('workspaces-remove') @@ -349,6 +356,20 @@ const previewBackground = createPreviewBackgroundController({ }, }) +const previewFont = createPreviewFontController({ + previewFontCssUrlInput: workspacesFontCssUrlInput, + getDefaultPreviewFontCssUrl: () => defaultPreviewFontCssUrl, + onFontConfigChange: ({ fontCssUrl, fontFamily }) => { + if (renderRuntime && typeof renderRuntime.updatePreviewFont === 'function') { + renderRuntime.updatePreviewFont({ fontCssUrl, fontFamily }) + } + + if (typeof queueWorkspaceSave === 'function') { + queueWorkspaceSave() + } + }, +}) + const layoutTheme = createLayoutThemeController({ appThemeButtons, syncPreviewBackgroundPickerFromTheme: () => @@ -757,7 +778,9 @@ const workspaceSyncController = createWorkspaceSyncController({ getActiveWorkspaceRecordId: () => activeWorkspaceRecordId, getActiveWorkspaceCreatedAt: () => activeWorkspaceCreatedAt, getRenderModeValue: () => renderMode.value, + getPreviewFontCssUrlValue: () => previewFont.getPreviewFontCssUrl(), normalizeRenderMode: mode => normalizeRenderMode(mode), + normalizePreviewFontCssUrl, }) const getTypecheckSourcePath = () => @@ -891,10 +914,15 @@ const { workspaceTabsState, resolveWorkspaceActiveTabId, normalizeRenderMode: mode => normalizeRenderMode(mode), + normalizePreviewFontCssUrl, getRenderModeValue: () => renderMode.value, + getPreviewFontCssUrlValue: () => previewFont.getPreviewFontCssUrl(), setRenderModeValue: value => { renderMode.value = value }, + setPreviewFontCssUrlValue: (value, options) => { + previewFont.applyPreviewFontCssUrl(value, options) + }, getActiveWorkspaceTab, onActiveWorkspaceTabChange: (_tab, { changed } = {}) => { syncDiagnosticsDrawerLayout() @@ -1201,6 +1229,8 @@ const githubWorkflows = createGitHubWorkflowsSetup({ workspacesShare, workspacesNew, workspacesSelect, + workspacesFontCssUrlInput, + workspacesFontCssUrlLoad, workspacesOpen, workspacesRename, workspacesRemove, @@ -1220,6 +1250,14 @@ const githubWorkflows = createGitHubWorkflowsSetup({ listLocalContextRecords, refreshLocalContextOptions, applyWorkspaceRecord, + applyWorkspaceFontCssUrl: async fontCssUrl => { + previewFont.applyPreviewFontCssUrl(fontCssUrl, { + emitChange: true, + syncInputValue: true, + }) + await flushWorkspaceSave({ preserveRecordId: true }) + return true + }, syncActiveWorkspaceRepositoryScope, forkWorkspaceFromCurrentState, flushWorkspaceSave, @@ -1424,6 +1462,7 @@ const runtimeCoreOptions = createRuntimeCoreOptions({ getRenderRuntime: () => renderRuntime, getPreviewHost: () => previewHost, previewBackground, + previewFont, clearDiagnosticsScope, clearConfirmDialog, clearConfirmTitle, @@ -1602,6 +1641,7 @@ bindAppEventsAndStart({ typeDiagnostics, clipboardSupported, previewBackground, + previewFont, initializeCodeEditors, }, }) diff --git a/src/index.html b/src/index.html index 4c30df0..969df8a 100644 --- a/src/index.html +++ b/src/index.html @@ -806,6 +806,26 @@

Workspaces

+
+ + +
+ diff --git a/src/modules/app-core/github-workflows.js b/src/modules/app-core/github-workflows.js index a5aa12a..482f158 100644 --- a/src/modules/app-core/github-workflows.js +++ b/src/modules/app-core/github-workflows.js @@ -551,11 +551,11 @@ const initializeGitHubWorkflows = ({ } try { - await applyWorkspaceFontCssUrl(fontCssUrl) + const loadResult = await applyWorkspaceFontCssUrl(fontCssUrl) if (typeof scheduleRender === 'function') { await Promise.resolve(scheduleRender()) } - return true + return loadResult } catch { workspacesDrawerController?.setStatus('Could not load font CSS URL.', 'error') return false diff --git a/src/modules/app-core/preview-font-setup.js b/src/modules/app-core/preview-font-setup.js new file mode 100644 index 0000000..6619741 --- /dev/null +++ b/src/modules/app-core/preview-font-setup.js @@ -0,0 +1,25 @@ +const createPreviewFontSetup = ({ + createPreviewFontController, + previewFontCssUrlInput, + defaultPreviewFontCssUrl, + getRenderRuntime, + queueWorkspaceSave, +}) => { + return createPreviewFontController({ + previewFontCssUrlInput, + getDefaultPreviewFontCssUrl: () => defaultPreviewFontCssUrl, + onFontConfigChange: ({ fontCssUrl, fontFamily }) => { + const renderRuntime = + typeof getRenderRuntime === 'function' ? getRenderRuntime() : null + if (renderRuntime && typeof renderRuntime.updatePreviewFont === 'function') { + renderRuntime.updatePreviewFont({ fontCssUrl, fontFamily }) + } + + if (typeof queueWorkspaceSave === 'function') { + queueWorkspaceSave() + } + }, + }) +} + +export { createPreviewFontSetup } diff --git a/src/modules/app-core/workspace-font-css-url-load.js b/src/modules/app-core/workspace-font-css-url-load.js new file mode 100644 index 0000000..c02a571 --- /dev/null +++ b/src/modules/app-core/workspace-font-css-url-load.js @@ -0,0 +1,38 @@ +const createApplyWorkspaceFontCssUrl = ({ + previewFont, + flushWorkspaceSave, + normalizePreviewFontCssUrl, + defaultPreviewFontCssUrl, +}) => { + return async fontCssUrl => { + const requestedFontCssUrl = typeof fontCssUrl === 'string' ? fontCssUrl.trim() : '' + const normalizedFontCssUrl = normalizePreviewFontCssUrl(requestedFontCssUrl) + const defaultNormalizedFontCssUrl = normalizePreviewFontCssUrl( + defaultPreviewFontCssUrl, + ) + const rejectedInput = + requestedFontCssUrl.length > 0 && + normalizedFontCssUrl === defaultNormalizedFontCssUrl && + requestedFontCssUrl !== defaultNormalizedFontCssUrl + + previewFont.applyPreviewFontCssUrl(requestedFontCssUrl, { + emitChange: true, + syncInputValue: true, + }) + + await flushWorkspaceSave({ preserveRecordId: true }) + + return { + ok: !rejectedInput, + status: rejectedInput + ? 'rejected' + : requestedFontCssUrl !== normalizedFontCssUrl + ? 'normalized' + : 'loaded', + requestedFontCssUrl, + normalizedFontCssUrl, + } + } +} + +export { createApplyWorkspaceFontCssUrl } diff --git a/src/modules/font-css-url.js b/src/modules/font-css-url.js new file mode 100644 index 0000000..977e265 --- /dev/null +++ b/src/modules/font-css-url.js @@ -0,0 +1,38 @@ +const defaultFontCssUrl = + 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap' + +const resolveCurrentProtocol = () => { + if (typeof window === 'undefined' || !window?.location?.protocol) { + return '' + } + + return String(window.location.protocol).toLowerCase() +} + +const normalizeFontCssUrl = ( + value, + { fallback = defaultFontCssUrl, currentProtocol = resolveCurrentProtocol() } = {}, +) => { + const normalized = typeof value === 'string' ? value.trim() : '' + if (!normalized) { + return fallback + } + + try { + const parsed = new URL(normalized) + const protocol = parsed.protocol.toLowerCase() + const allowHttp = String(currentProtocol).toLowerCase() === 'http:' + if (protocol === 'http:' && !allowHttp) { + return fallback + } + if (protocol !== 'https:' && protocol !== 'http:') { + return fallback + } + + return parsed.href + } catch { + return fallback + } +} + +export { defaultFontCssUrl, normalizeFontCssUrl } diff --git a/src/modules/preview-runtime/iframe-preview-executor.js b/src/modules/preview-runtime/iframe-preview-executor.js index 44c60a1..3fd4f19 100644 --- a/src/modules/preview-runtime/iframe-preview-executor.js +++ b/src/modules/preview-runtime/iframe-preview-executor.js @@ -100,6 +100,11 @@ const createIframeShellDocument = ({ channelId, parentOrigin, importMap }) => { try { const parsed = new URL(normalized, window.location.href) const protocol = parsed.protocol.toLowerCase() + const currentProtocol = String(window.location.protocol || '').toLowerCase() + const allowHttp = currentProtocol === 'http:' + if (protocol === 'http:' && !allowHttp) { + return '' + } if (protocol !== 'https:' && protocol !== 'http:') { return '' } diff --git a/src/modules/preview/preview-font.js b/src/modules/preview/preview-font.js index 0aa81db..0699fab 100644 --- a/src/modules/preview/preview-font.js +++ b/src/modules/preview/preview-font.js @@ -1,26 +1,12 @@ +import { defaultFontCssUrl, normalizeFontCssUrl } from '../font-css-url.js' + const systemSansFontFamily = 'system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif' -const defaultPreviewFontCssUrl = - 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap' - -const normalizePreviewFontCssUrl = value => { - const normalized = typeof value === 'string' ? value.trim() : '' - if (!normalized) { - return defaultPreviewFontCssUrl - } +const defaultPreviewFontCssUrl = defaultFontCssUrl - try { - const parsed = new URL(normalized) - const protocol = parsed.protocol.toLowerCase() - if (protocol !== 'https:' && protocol !== 'http:') { - return defaultPreviewFontCssUrl - } - return parsed.href - } catch { - return defaultPreviewFontCssUrl - } -} +const normalizePreviewFontCssUrl = value => + normalizeFontCssUrl(value, { fallback: defaultPreviewFontCssUrl }) const decodeGoogleFontsFamilyName = value => { if (typeof value !== 'string' || value.length === 0) { diff --git a/src/modules/workspace/workspace-storage.js b/src/modules/workspace/workspace-storage.js index 81cacaa..966d968 100644 --- a/src/modules/workspace/workspace-storage.js +++ b/src/modules/workspace/workspace-storage.js @@ -1,4 +1,5 @@ import { cdnImports, importFromCdnWithFallback } from '../cdn.js' +import { defaultFontCssUrl, normalizeFontCssUrl } from '../font-css-url.js' import { toWorkspaceRecordKey } from './workspace-tab-helpers.js' const workspaceDbName = 'knighted-develop-workspaces' @@ -23,26 +24,8 @@ const toTabRole = value => { const normalizeRenderMode = value => (value === 'react' ? 'react' : 'dom') -const defaultFontCssUrl = - 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap' - -const normalizePreviewFontCssUrl = value => { - const normalized = typeof value === 'string' ? value.trim() : '' - if (!normalized) { - return defaultFontCssUrl - } - - try { - const parsed = new URL(normalized) - const protocol = parsed.protocol.toLowerCase() - if (protocol !== 'https:' && protocol !== 'http:') { - return defaultFontCssUrl - } - return parsed.href - } catch { - return defaultFontCssUrl - } -} +const normalizePreviewFontCssUrl = value => + normalizeFontCssUrl(value, { fallback: defaultFontCssUrl }) const toSyncTimestamp = value => Number.isFinite(value) && value > 0 ? Math.max(0, Number(value)) : null @@ -198,12 +181,31 @@ const backfillWorkspaceFontCssUrlField = async db => { return } - const recordIds = records + const candidateRecordIds = records + .filter(record => { + if (!record || typeof record !== 'object') { + return false + } + + const normalizedFontCssUrl = normalizePreviewFontCssUrl( + record.fontCssUrl ?? record.previewFontCssUrl, + ) + + return ( + !hasOwnRecordValue(record, 'fontCssUrl') || + typeof record.fontCssUrl !== 'string' || + record.fontCssUrl !== normalizedFontCssUrl + ) + }) .map(record => (typeof record?.id === 'string' ? record.id : '')) .filter(Boolean) + if (candidateRecordIds.length === 0) { + return + } + await Promise.all( - recordIds.map(async recordId => { + candidateRecordIds.map(async recordId => { const record = await db.get(workspaceStoreName, recordId) if (!record || typeof record !== 'object') { return @@ -249,7 +251,7 @@ export const createWorkspaceStorageAdapter = ({ loadRuntime } = {}) => { }) } - return Promise.all([dbPromise, fontCssUrlBackfillPromise]).then(([db]) => db) + return dbPromise } const getWorkspaceById = async id => { diff --git a/src/modules/workspace/workspaces-drawer/drawer.js b/src/modules/workspace/workspaces-drawer/drawer.js index 3bf5dde..be0a0b6 100644 --- a/src/modules/workspace/workspaces-drawer/drawer.js +++ b/src/modules/workspace/workspaces-drawer/drawer.js @@ -569,18 +569,31 @@ export const createWorkspacesDrawer = ({ const fontCssUrlValue = fontCssUrlInput instanceof HTMLInputElement ? toSafeText(fontCssUrlInput.value) : '' - let loaded = false + let loadResult = false try { - loaded = await onLoadFontCssUrl(fontCssUrlValue) + loadResult = await onLoadFontCssUrl(fontCssUrlValue) } catch { setStatus('Could not load font CSS URL.', 'error') return } - if (!loaded) { + if (!loadResult) { return } + if (loadResult && typeof loadResult === 'object') { + const status = toSafeText(loadResult.status) + if (status === 'rejected') { + setStatus('Invalid font CSS URL. Loaded default value instead.', 'error') + return + } + + if (status === 'normalized') { + setStatus('Loaded normalized font CSS URL.', 'neutral') + return + } + } + setStatus('Loaded font CSS URL.', 'neutral') }) From f4f3b35436b44b34a16912cbdf3b3004bcaa2a37 Mon Sep 17 00:00:00 2001 From: KCM Date: Thu, 7 May 2026 23:05:42 -0500 Subject: [PATCH 3/4] test: reduce flake. --- .../active-context-sync.spec.ts | 9 ++- playwright/helpers/app-test-helpers.ts | 76 ++++++++++++++----- 2 files changed, 62 insertions(+), 23 deletions(-) diff --git a/playwright/github-pr-drawer/active-context-sync.spec.ts b/playwright/github-pr-drawer/active-context-sync.spec.ts index 89c3c55..a856018 100644 --- a/playwright/github-pr-drawer/active-context-sync.spec.ts +++ b/playwright/github-pr-drawer/active-context-sync.spec.ts @@ -10,6 +10,7 @@ import { getWorkspaceTabsRecord, mockRepositoryBranches, openMostRecentStoredWorkspaceContext, + openStoredWorkspaceContextByHead, renameWorkspaceTab, seedActivePrWorkspaceContext, seedLocalWorkspaceContexts, @@ -1304,7 +1305,6 @@ test('Active PR context push commit uses Git Database API atomic path by default await connectByotWithSingleRepo(page) await openMostRecentStoredWorkspaceContext(page) await ensureOpenPrDrawerOpen(page) - await setComponentEditorSource(page, 'const commitMarker = 2') await setStylesEditorSource(page, '.commit-marker { color: blue; }') const pushCommitMessage = 'chore: push active context sync (atomic)' @@ -1563,8 +1563,11 @@ test('Open PR uses module tab paths when stale target file paths collide', async }, ]) - await connectByotWithSingleRepo(page) - await openMostRecentStoredWorkspaceContext(page) + await connectByotWithSingleRepo(page, { + autoOpenWorkspace: false, + assertPrRepositorySelected: false, + }) + await openStoredWorkspaceContextByHead(page, 'develop/open-pr-stale-target-paths') await ensureOpenPrDrawerOpen(page) const commitMessage = 'chore: open pr with stale module target path metadata' diff --git a/playwright/helpers/app-test-helpers.ts b/playwright/helpers/app-test-helpers.ts index 4447f47..ef2772a 100644 --- a/playwright/helpers/app-test-helpers.ts +++ b/playwright/helpers/app-test-helpers.ts @@ -2,6 +2,7 @@ import { expect } from '@playwright/test' import type { Page } from '@playwright/test' const webServerMode = process.env.PLAYWRIGHT_WEB_SERVER_MODE ?? 'dev' +const pagesWithStubbedExternalFonts = new WeakSet() export const appEntryPath = webServerMode === 'preview' ? '/index.html' : '/src/index.html' @@ -71,7 +72,34 @@ const navigateToApp = async (page: Page, path: string) => { } } +const stubExternalFontRequests = async (page: Page) => { + if (pagesWithStubbedExternalFonts.has(page)) { + return + } + + pagesWithStubbedExternalFonts.add(page) + + await page.route('https://fonts.googleapis.com/**', async route => { + await route.fulfill({ + status: 200, + contentType: 'text/css; charset=utf-8', + body: '', + headers: { + 'cache-control': 'public, max-age=31536000, immutable', + }, + }) + }) + + await page.route('https://fonts.gstatic.com/**', async route => { + await route.fulfill({ + status: 204, + body: '', + }) + }) +} + export const waitForAppReady = async (page: Page, path = appEntryPath) => { + await stubExternalFontRequests(page) await navigateToApp(page, path) await expect(page.getByRole('heading', { name: '@knighted/develop' })).toBeVisible() await expect @@ -483,8 +511,12 @@ export const connectByotWithSingleRepo = async ( page: Page, { branchesByRepo, + autoOpenWorkspace = true, + assertPrRepositorySelected = true, }: { branchesByRepo?: BranchesByRepo + autoOpenWorkspace?: boolean + assertPrRepositorySelected?: boolean } = {}, ) => { await page.route('https://api.github.com/user/repos**', async route => { @@ -525,33 +557,37 @@ export const connectByotWithSingleRepo = async ( await workspacesRepositoryFilter.selectOption('knightedcodemonkey/develop') await expect(workspacesRepositoryFilter).toHaveValue('knightedcodemonkey/develop') - const initializeButton = page.getByRole('button', { - name: 'Initialize', - exact: true, - }) + if (autoOpenWorkspace) { + const initializeButton = page.getByRole('button', { + name: 'Initialize', + exact: true, + }) - if (await initializeButton.isVisible()) { - await initializeButton.click() - } else { - const storedWorkspace = page.getByLabel('Stored workspace') - if (await storedWorkspace.isVisible()) { - const workspaceValue = await storedWorkspace - .locator('option:not([value=""])') - .first() - .getAttribute('value') - - if (workspaceValue) { - await storedWorkspace.selectOption(workspaceValue) - await page.getByRole('button', { name: 'Open', exact: true }).click() + if (await initializeButton.isVisible()) { + await initializeButton.click() + } else { + const storedWorkspace = page.getByLabel('Stored workspace') + if (await storedWorkspace.isVisible()) { + const workspaceValue = await storedWorkspace + .locator('option:not([value=""])') + .first() + .getAttribute('value') + + if (workspaceValue) { + await storedWorkspace.selectOption(workspaceValue) + await page.getByRole('button', { name: 'Open', exact: true }).click() + } } } } await ensureWorkspacesDrawerClosed(page) - const repoSelect = page.getByLabel('Pull request repository') - await expect(repoSelect).toHaveValue('knightedcodemonkey/develop') - await expect(repoSelect).toBeDisabled() + if (assertPrRepositorySelected) { + const repoSelect = page.getByLabel('Pull request repository') + await expect(repoSelect).toHaveValue('knightedcodemonkey/develop') + await expect(repoSelect).toBeDisabled() + } await expect( page.getByRole('button', { From ff06bbc116e47ed51be01384fba1657ac5995fbd Mon Sep 17 00:00:00 2001 From: KCM Date: Fri, 8 May 2026 10:19:31 -0500 Subject: [PATCH 4/4] refactor: address pr comments. --- AGENTS.md | 1 + playwright/rendering-modes/core.spec.ts | 4 +- src/index.html | 6 +- .../iframe-preview-executor.js | 58 ++++++++++++-- src/modules/preview/preview-font.js | 7 -- src/modules/workspace/workspace-storage.js | 76 +++++++------------ 6 files changed, 83 insertions(+), 69 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 42cdb76..c6f5acd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -102,3 +102,4 @@ Never: - Edit generated output folders unless explicitly requested. - Modify node_modules or lockfiles unless explicitly requested. - Reintroduce cross-workspace overwrite/delete behavior with any changes. +- Use eslint disable comments. diff --git a/playwright/rendering-modes/core.spec.ts b/playwright/rendering-modes/core.spec.ts index 74119fb..08b601a 100644 --- a/playwright/rendering-modes/core.spec.ts +++ b/playwright/rendering-modes/core.spec.ts @@ -243,12 +243,12 @@ test('workspace font CSS URL applies to preview and persists per workspace', asy await page.getByRole('button', { name: 'Workspaces' }).click() const fontCssUrlInput = page.getByRole('textbox', { - name: 'Workspace Google Fonts CSS URL', + name: 'Workspace font stylesheet URL', }) await fontCssUrlInput.fill( 'https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;700&display=swap', ) - await fontCssUrlInput.press('Tab') + await page.getByRole('button', { name: 'Load', exact: true }).click() await expect .poll(async () => (await readPreviewBodyFontFamily(page)).toLowerCase()) diff --git a/src/index.html b/src/index.html index aefab8e..8e08ae1 100644 --- a/src/index.html +++ b/src/index.html @@ -811,14 +811,14 @@

Workspaces

class="github-pr-field github-pr-field--full" for="workspaces-font-css-url" > - Google Fonts CSS URL + Font Stylesheet URL