Skip to content

Commit 2d1ee4e

Browse files
fix: require app in entry, hide workspaces without pat, remove star important. (#86)
1 parent 8ae4b9a commit 2d1ee4e

14 files changed

Lines changed: 104 additions & 213 deletions

playwright.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ if (isCI || includeWebKit) {
3030

3131
export default defineConfig({
3232
testDir: 'playwright',
33+
fullyParallel: isCI,
3334
timeout: isCI ? 120_000 : 20_000,
3435
retries: isCI ? 1 : 0,
3536
workers: isCI ? 1 : undefined,

playwright/github-byot-ai.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,28 @@ test('PR/BYOT controls are visible and chat stays hidden until token connect', a
2323
exact: true,
2424
includeHidden: true,
2525
})
26+
const workspacesToggle = page.getByRole('button', {
27+
name: 'Workspaces',
28+
exact: true,
29+
includeHidden: true,
30+
})
2631
await expect(byotControls).toBeVisible()
2732
await expect(page.getByRole('textbox', { name: 'GitHub token' })).toBeVisible()
2833
await expect(page.getByRole('button', { name: 'Add GitHub token' })).toBeVisible()
2934
await expect(page.getByRole('button', { name: 'Chat' })).toBeHidden()
3035
await expect(page.getByRole('heading', { name: 'AI Chat' })).toBeHidden()
3136
await expect(prToggle).toHaveCount(1)
3237
await expect(prToggle).toBeHidden()
38+
await expect(workspacesToggle).toHaveCount(1)
39+
await expect(workspacesToggle).toBeHidden()
3340
})
3441

3542
test('chat becomes available after token connect', async ({ page }) => {
3643
await waitForAppReady(page)
3744
await connectByotWithSingleRepo(page)
3845

3946
await expect(page.getByRole('button', { name: 'Open pull request' })).toBeVisible()
47+
await expect(page.getByRole('button', { name: 'Workspaces' })).toBeVisible()
4048
await expect(page.getByRole('button', { name: 'Chat' })).toBeVisible()
4149
})
4250

@@ -49,12 +57,19 @@ test('BYOT controls render with default app entry', async ({ page }) => {
4957
exact: true,
5058
includeHidden: true,
5159
})
60+
const workspacesToggle = page.getByRole('button', {
61+
name: 'Workspaces',
62+
exact: true,
63+
includeHidden: true,
64+
})
5265
await expect(byotControls).toBeVisible()
5366
await expect(page.getByRole('textbox', { name: 'GitHub token' })).toBeVisible()
5467
await expect(page.getByRole('button', { name: 'Add GitHub token' })).toBeVisible()
5568
await expect(page.getByRole('button', { name: 'Chat' })).toBeHidden()
5669
await expect(prToggle).toHaveCount(1)
5770
await expect(prToggle).toBeHidden()
71+
await expect(workspacesToggle).toHaveCount(1)
72+
await expect(workspacesToggle).toBeHidden()
5873
})
5974

6075
test('GitHub token info panel reflects missing and present token states', async ({

playwright/helpers/app-test-helpers.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,22 @@ export const openWorkspaceTab = async (page: Page, fileName: string) => {
171171
await page.getByRole('button', { name: pattern }).click()
172172
}
173173

174+
const replaceEditorSource = async ({
175+
editorContent,
176+
source,
177+
}: {
178+
editorContent: ReturnType<Page['locator']>
179+
source: string
180+
}) => {
181+
for (let attempt = 0; attempt < 2; attempt += 1) {
182+
await editorContent.fill('')
183+
await editorContent.fill(source)
184+
await editorContent.press('End')
185+
await editorContent.type(' ')
186+
await editorContent.press('Backspace')
187+
}
188+
}
189+
174190
export const reorderWorkspaceTabBefore = async (
175191
page: Page,
176192
{ from, to }: { from: string; to: string },
@@ -199,35 +215,29 @@ export const setWorkspaceTabSource = async (
199215
},
200216
) => {
201217
await openWorkspaceTab(page, fileName)
218+
await expect(page.getByRole('region', { name: fileName })).toBeVisible()
202219
const editorContent = page
203220
.locator(`.editor-panel[data-editor-kind="${kind}"] .cm-content`)
204221
.first()
205-
await editorContent.fill(source)
206-
await editorContent.press('End')
207-
await editorContent.type(' ')
208-
await editorContent.press('Backspace')
222+
await replaceEditorSource({ editorContent, source })
209223
}
210224

211225
export const setComponentEditorSource = async (page: Page, source: string) => {
212226
await page.getByRole('button', { name: 'Open tab App.tsx' }).click()
227+
await expect(page.getByRole('region', { name: 'App.tsx' })).toBeVisible()
213228
const editorContent = page
214229
.locator('.editor-panel[data-editor-kind="component"] .cm-content')
215230
.first()
216-
await editorContent.fill(source)
217-
await editorContent.press('End')
218-
await editorContent.type(' ')
219-
await editorContent.press('Backspace')
231+
await replaceEditorSource({ editorContent, source })
220232
}
221233

222234
export const setStylesEditorSource = async (page: Page, source: string) => {
223235
await page.getByRole('button', { name: 'Open tab app.css' }).click()
236+
await expect(page.getByRole('region', { name: 'app.css' })).toBeVisible()
224237
const editorContent = page
225238
.locator('.editor-panel[data-editor-kind="styles"] .cm-content')
226239
.first()
227-
await editorContent.fill(source)
228-
await editorContent.press('End')
229-
await editorContent.type(' ')
230-
await editorContent.press('Backspace')
240+
await replaceEditorSource({ editorContent, source })
231241
}
232242

233243
export const getActiveComponentEditorLineNumber = async (page: Page) => {

playwright/rendering-modes.spec.ts

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ test('shows App-only error when auto render is disabled and App is missing', asy
577577
)
578578
})
579579

580-
test('auto render implicitly wraps source with App in dom and react modes', async ({
580+
test('auto render shows App-only error in dom and react modes when App is missing', async ({
581581
page,
582582
}) => {
583583
await waitForInitialRender(page)
@@ -589,9 +589,9 @@ test('auto render implicitly wraps source with App in dom and react modes', asyn
589589
'const Button = () => <button type="button">implicit app dom</button>',
590590
)
591591

592-
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Rendered')
593-
await expect(getPreviewFrame(page).getByRole('button')).toContainText(
594-
'implicit app dom',
592+
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Error')
593+
await expect(page.locator('#preview-host pre')).toContainText(
594+
'Expected a function or const named App.',
595595
)
596596

597597
await page.getByRole('combobox', { name: 'Render mode' }).selectOption('react')
@@ -604,13 +604,13 @@ test('auto render implicitly wraps source with App in dom and react modes', asyn
604604
page.locator('.editor-panel[data-editor-kind="component"] .cm-content').first(),
605605
).toContainText('implicit app react')
606606

607-
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Rendered')
608-
await expect(getPreviewFrame(page).getByRole('button')).toContainText(
609-
'implicit app react',
607+
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Error')
608+
await expect(page.locator('#preview-host pre')).toContainText(
609+
'Expected a function or const named App.',
610610
)
611611
})
612612

613-
test('auto render implicit App includes multiple component declarations', async ({
613+
test('auto render renders successfully when explicit App is defined in dom and react modes', async ({
614614
page,
615615
}) => {
616616
await waitForInitialRender(page)
@@ -619,15 +619,25 @@ test('auto render implicit App includes multiple component declarations', async
619619

620620
await setComponentEditorSource(
621621
page,
622-
[
623-
'const OtherButton = () => <button type="button">bar</button>',
624-
'const Button = () => <button type="button">foo</button>',
625-
].join('\n'),
622+
'const App = () => <button type="button">explicit app dom</button>',
626623
)
627624

628625
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Rendered')
629-
await expect(getPreviewFrame(page).getByRole('button')).toHaveCount(2)
630-
await expect(getPreviewFrame(page).getByRole('button')).toContainText(['bar', 'foo'])
626+
await expect(getPreviewFrame(page).getByRole('button')).toContainText(
627+
'explicit app dom',
628+
)
629+
630+
await page.getByRole('combobox', { name: 'Render mode' }).selectOption('react')
631+
await expect(page.getByRole('combobox', { name: 'Render mode' })).toHaveValue('react')
632+
await setComponentEditorSource(
633+
page,
634+
'const App = () => <button type="button">explicit app react</button>',
635+
)
636+
637+
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Rendered')
638+
await expect(getPreviewFrame(page).getByRole('button')).toContainText(
639+
'explicit app react',
640+
)
631641
})
632642

633643
test('auto render does not treat lowercase helpers as implicit components', async ({
@@ -651,7 +661,7 @@ test('auto render does not treat lowercase helpers as implicit components', asyn
651661
)
652662
})
653663

654-
test('auto render wraps standalone JSX with trailing semicolon and comment', async ({
664+
test('auto render shows App-only error for standalone JSX expression', async ({
655665
page,
656666
}) => {
657667
await waitForInitialRender(page)
@@ -663,13 +673,13 @@ test('auto render wraps standalone JSX with trailing semicolon and comment', asy
663673
'(<button type="button">implicit app from jsx expression</button>) as any; // trailing',
664674
)
665675

666-
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Rendered')
667-
await expect(getPreviewFrame(page).getByRole('button')).toContainText(
668-
'implicit app from jsx expression',
676+
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Error')
677+
await expect(page.locator('#preview-host pre')).toContainText(
678+
'Expected a function or const named App.',
669679
)
670680
})
671681

672-
test('auto render requires explicit App for declarations plus top-level JSX expression', async ({
682+
test('auto render shows App-only error for declarations plus top-level JSX expression', async ({
673683
page,
674684
}) => {
675685
await waitForInitialRender(page)
@@ -687,7 +697,7 @@ test('auto render requires explicit App for declarations plus top-level JSX expr
687697

688698
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Error')
689699
await expect(page.locator('#preview-host pre')).toContainText(
690-
'Top-level JSX with declarations or imports requires an explicit App component.',
700+
'Expected a function or const named App.',
691701
)
692702
})
693703

src/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ <h1>
165165
aria-controls="workspaces-drawer"
166166
title="Manage local workspaces"
167167
disabled
168+
hidden
168169
>
169170
<svg class="workspaces-toggle__icon" viewBox="0 0 24 24" aria-hidden="true">
170171
<path

src/modules/app-core/app-composition-options.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ const createRuntimeCoreOptions = ({
9191
cdnImports,
9292
importFromCdnWithFallback,
9393
renderMode,
94-
isAutoRenderEnabled: () => autoRenderToggle.checked,
9594
getJsxSource,
9695
getWorkspaceTabs: () => buildWorkspaceTabsSnapshot(),
9796
getPreviewHost,

src/modules/app-core/github-pr-context-ui.js

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,25 +111,35 @@ export const createGitHubPrContextUiController = ({
111111
workspacesToggle.disabled = false
112112
}
113113

114-
aiChatToggle?.removeAttribute('hidden')
114+
if (aiChatToggle instanceof HTMLElement) {
115+
aiChatToggle.hidden = false
116+
}
115117

116-
githubPrToggle?.removeAttribute('hidden')
118+
if (githubPrToggle instanceof HTMLElement) {
119+
githubPrToggle.hidden = false
120+
}
117121
if (!contextState.activePrContext) {
118-
workspacesToggle?.removeAttribute('hidden')
122+
if (workspacesToggle instanceof HTMLElement) {
123+
workspacesToggle.hidden = false
124+
}
119125
}
120126

121127
if (contextState.activePrContext) {
122128
githubPrContextClose?.removeAttribute('hidden')
123129
githubPrContextDisconnect?.removeAttribute('hidden')
124-
workspacesToggle?.setAttribute('hidden', '')
130+
if (workspacesToggle instanceof HTMLElement) {
131+
workspacesToggle.hidden = true
132+
}
125133
} else {
126134
githubPrContextClose?.setAttribute('hidden', '')
127135
githubPrContextDisconnect?.setAttribute('hidden', '')
128136
}
129137
return
130138
}
131139

132-
aiChatToggle?.setAttribute('hidden', '')
140+
if (aiChatToggle instanceof HTMLElement) {
141+
aiChatToggle.hidden = true
142+
}
133143
aiChatToggle?.setAttribute('aria-expanded', 'false')
134144
if (workspacesToggle instanceof HTMLButtonElement) {
135145
workspacesToggle.disabled = true
@@ -139,9 +149,13 @@ export const createGitHubPrContextUiController = ({
139149
contextState.hasSyncedActivePrEditorContent = false
140150
syncEditorPrContextIndicators(false)
141151
setGitHubPrToggleVisual('open-pr')
142-
githubPrToggle?.setAttribute('hidden', '')
152+
if (githubPrToggle instanceof HTMLElement) {
153+
githubPrToggle.hidden = true
154+
}
143155
githubPrToggle?.setAttribute('aria-expanded', 'false')
144-
workspacesToggle?.setAttribute('hidden', '')
156+
if (workspacesToggle instanceof HTMLElement) {
157+
workspacesToggle.hidden = true
158+
}
145159
workspacesToggle?.setAttribute('aria-expanded', 'false')
146160
githubPrContextClose?.setAttribute('hidden', '')
147161
githubPrContextDisconnect?.setAttribute('hidden', '')

src/modules/app-core/github-workflows.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,6 @@ const initializeGitHubWorkflows = ({
187187
onActivePrContextChange: activeContext => {
188188
prContextUi.setActivePrContext(activeContext)
189189
prContextUi.syncAiChatTokenVisibility(getTokenForVisibility())
190-
if (workspacesToggle instanceof HTMLButtonElement) {
191-
workspacesToggle.hidden = Boolean(activeContext)
192-
}
193190

194191
if (activeContext) {
195192
closeWorkspacesDrawer()

src/modules/app-core/workspace-controllers-setup.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,6 @@ const createWorkspaceControllersSetup = ({
111111
const setActiveWorkspaceTab = tabId =>
112112
workspaceTabSelectionController.setActiveWorkspaceTab(tabId)
113113

114-
const syncEditorFromActiveWorkspaceTabDelegate = () =>
115-
workspaceTabSelectionController.syncEditorFromActiveWorkspaceTab()
116-
117114
workspaceTabMutationsController = createWorkspaceTabMutationsController({
118115
toNonEmptyWorkspaceText,
119116
workspaceTabsState,
@@ -186,7 +183,6 @@ const createWorkspaceControllersSetup = ({
186183
getShouldShowEditedDesign,
187184
workspaceTabsShell,
188185
workspaceTabAddWrap,
189-
syncEditorFromActiveWorkspaceTab: syncEditorFromActiveWorkspaceTabDelegate,
190186
})
191187

192188
const workspaceContextController = createWorkspaceContextController({

src/modules/app-core/workspace-tab-selection-controller.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,8 @@ const createWorkspaceTabSelectionController = ({
4747
})
4848
}
4949

50-
const syncEditorFromActiveWorkspaceTab = () => {
51-
const activeTab = getActiveWorkspaceTab()
52-
if (!activeTab) {
53-
return
54-
}
55-
56-
loadWorkspaceTabIntoEditor(activeTab)
57-
}
58-
5950
return {
6051
setActiveWorkspaceTab,
61-
syncEditorFromActiveWorkspaceTab,
6252
}
6353
}
6454

0 commit comments

Comments
 (0)