Skip to content

Commit af26c6e

Browse files
feat: release ai chat. (#56)
1 parent d1145c5 commit af26c6e

File tree

7 files changed

+40
-139
lines changed

7 files changed

+40
-139
lines changed

README.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,6 @@ browser acts as the runtime host for render, lint, and typecheck flows.
4141

4242
- GitHub PAT setup and usage: [docs/byot.md](docs/byot.md)
4343

44-
AI chat features remain opt-in behind
45-
`?feature-ai=true`.
46-
4744
## Fine-Grained PAT Quick Setup
4845

4946
For PR/BYOT and AI chat flows, use a fine-grained GitHub PAT and follow the

docs/byot.md

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,14 @@ BYOT controls are available by default. The token is used to:
1111
- let you choose which repository to work with
1212
- use PR context features (Open PR / Push Commit flows)
1313

14-
When AI chat is enabled, the same token is also used for GitHub Models requests.
14+
The same token is also used for GitHub Models requests in AI chat flows.
1515

1616
## Privacy and storage behavior
1717

1818
- Your token is stored only in your browser `localStorage`.
1919
- The token is never sent to any service except the GitHub endpoints required by the feature.
2020
- You can remove it at any time using the delete button in the BYOT controls.
2121

22-
## Enable AI chat features
23-
24-
BYOT/PR controls do not require a feature flag. To enable AI chat features, use one
25-
of these options:
26-
27-
1. Add `?feature-ai=true` to the app URL.
28-
2. Set `localStorage` key `knighted:develop:feature:ai-assistant` to `true`.
29-
3022
## Create a fine-grained PAT
3123

3224
Create a fine-grained PAT in GitHub settings and grant the permissions below.
@@ -63,7 +55,7 @@ Use either of these scopes depending on your needs:
6355
3. Paste token into the BYOT input and click add.
6456
4. Verify repository list loads.
6557
5. Select your target repository.
66-
6. Optional: enable AI chat with `?feature-ai=true`.
58+
6. Use AI chat as needed after connecting your token.
6759

6860
## Screenshots
6961

playwright/github-byot-ai.spec.ts

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
waitForAppReady,
1313
} from './helpers/app-test-helpers.js'
1414

15-
test('PR/BYOT controls are visible without feature flag, but chat stays hidden', async ({
15+
test('PR/BYOT controls are visible and chat stays hidden until token connect', async ({
1616
page,
1717
}) => {
1818
await waitForAppReady(page)
@@ -32,18 +32,16 @@ test('PR/BYOT controls are visible without feature flag, but chat stays hidden',
3232
await expect(prToggle).toBeHidden()
3333
})
3434

35-
test('chat remains hidden without feature flag after token connect', async ({ page }) => {
35+
test('chat becomes available after token connect', async ({ page }) => {
3636
await waitForAppReady(page)
3737
await connectByotWithSingleRepo(page)
3838

3939
await expect(page.getByRole('button', { name: 'Open pull request' })).toBeVisible()
40-
await expect(page.getByRole('button', { name: 'Chat' })).toBeHidden()
40+
await expect(page.getByRole('button', { name: 'Chat' })).toBeVisible()
4141
})
4242

43-
test('BYOT controls render when feature flag is enabled by query param', async ({
44-
page,
45-
}) => {
46-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
43+
test('BYOT controls render with default app entry', async ({ page }) => {
44+
await waitForAppReady(page, appEntryPath)
4745

4846
const byotControls = page.getByRole('group', { name: 'GitHub controls' })
4947
const prToggle = page.getByRole('button', {
@@ -62,7 +60,7 @@ test('BYOT controls render when feature flag is enabled by query param', async (
6260
test('GitHub token info panel reflects missing and present token states', async ({
6361
page,
6462
}) => {
65-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
63+
await waitForAppReady(page, `${appEntryPath}`)
6664

6765
const infoButtonMissing = page.getByRole('button', {
6866
name: 'About GitHub token features and privacy',
@@ -107,7 +105,7 @@ test('GitHub token info panel reflects missing and present token states', async
107105
})
108106

109107
test('deleting saved GitHub token requires confirmation modal', async ({ page }) => {
110-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
108+
await waitForAppReady(page, `${appEntryPath}`)
111109
await connectByotWithSingleRepo(page)
112110

113111
const dialog = page.getByRole('dialog', {
@@ -154,8 +152,8 @@ test('deleting saved GitHub token requires confirmation modal', async ({ page })
154152
await expect(tokenInput).toHaveValue('')
155153
})
156154

157-
test('AI chat drawer opens and closes when feature flag is enabled', async ({ page }) => {
158-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
155+
test('AI chat drawer opens and closes', async ({ page }) => {
156+
await waitForAppReady(page, appEntryPath)
159157
await connectByotWithSingleRepo(page)
160158

161159
const chatToggle = page.getByRole('button', { name: 'Chat', exact: true })
@@ -193,7 +191,7 @@ test('AI chat prefers streaming responses when available', async ({ page }) => {
193191
})
194192
})
195193

196-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
194+
await waitForAppReady(page, `${appEntryPath}`)
197195
await connectByotWithSingleRepo(page)
198196
await ensureAiChatDrawerOpen(page)
199197

@@ -255,7 +253,7 @@ test('AI chat can disable editor context payload via checkbox', async ({ page })
255253
})
256254
})
257255

258-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
256+
await waitForAppReady(page, `${appEntryPath}`)
259257
await connectByotWithSingleRepo(page)
260258
await ensureAiChatDrawerOpen(page)
261259

@@ -350,7 +348,7 @@ test('AI chat proposals can be confirmed, applied, and undone for component and
350348
})
351349
})
352350

353-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
351+
await waitForAppReady(page, `${appEntryPath}`)
354352
await connectByotWithSingleRepo(page)
355353
await setComponentEditorSource(page, 'const App = () => <button>Before</button>')
356354
await setStylesEditorSource(page, '.button { color: red; }')
@@ -462,7 +460,7 @@ test('AI chat shows a single apply action when both editor proposals are availab
462460
})
463461
})
464462

465-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
463+
await waitForAppReady(page, `${appEntryPath}`)
466464
await connectByotWithSingleRepo(page)
467465
await setComponentEditorSource(page, 'const App = () => <button>Before</button>')
468466
await setStylesEditorSource(page, '.button { color: red; }')
@@ -560,7 +558,7 @@ test('AI chat streaming text still updates while latest undo actions are visible
560558
})
561559
})
562560

563-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
561+
await waitForAppReady(page, `${appEntryPath}`)
564562
await connectByotWithSingleRepo(page)
565563
await setStylesEditorSource(page, '.button { color: red; }')
566564
await ensureAiChatDrawerOpen(page)
@@ -626,7 +624,7 @@ test('AI chat falls back to non-streaming response when streaming fails', async
626624
})
627625
})
628626

629-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
627+
await waitForAppReady(page, `${appEntryPath}`)
630628
await connectByotWithSingleRepo(page)
631629
await ensureAiChatDrawerOpen(page)
632630

@@ -678,7 +676,7 @@ test('clearing chat removes previous conversation context from new request', asy
678676
})
679677
})
680678

681-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
679+
await waitForAppReady(page, `${appEntryPath}`)
682680
await connectByotWithSingleRepo(page)
683681
await ensureAiChatDrawerOpen(page)
684682

@@ -738,7 +736,7 @@ test('BYOT remembers selected repository across reloads', async ({ page }) => {
738736
'knightedcodemonkey/css': ['main', 'release/1.x'],
739737
})
740738

741-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
739+
await waitForAppReady(page, `${appEntryPath}`)
742740

743741
await page
744742
.getByRole('textbox', { name: 'GitHub token' })

playwright/github-pr-drawer.spec.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ test('Open PR drawer confirms and submits component/styles filepaths', async ({
189189
},
190190
)
191191

192-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
192+
await waitForAppReady(page, `${appEntryPath}`)
193193
await connectByotWithSingleRepo(page)
194194
await ensureOpenPrDrawerOpen(page)
195195

@@ -254,7 +254,7 @@ test('Open PR drawer confirms and submits component/styles filepaths', async ({
254254
test('Open PR drawer starts with empty title/description and short default head', async ({
255255
page,
256256
}) => {
257-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
257+
await waitForAppReady(page, `${appEntryPath}`)
258258
await connectByotWithSingleRepo(page)
259259
await ensureOpenPrDrawerOpen(page)
260260

@@ -309,7 +309,7 @@ test('Open PR drawer base dropdown updates from mocked repo branches', async ({
309309
})
310310
})
311311

312-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
312+
await waitForAppReady(page, `${appEntryPath}`)
313313

314314
await page
315315
.getByRole('textbox', { name: 'GitHub token' })
@@ -377,7 +377,7 @@ test('Open PR drawer keeps a single active PR context in localStorage', async ({
377377
'knightedcodemonkey/css': ['stable', 'release/1.x'],
378378
})
379379

380-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
380+
await waitForAppReady(page, `${appEntryPath}`)
381381

382382
await page
383383
.getByRole('textbox', { name: 'GitHub token' })
@@ -452,7 +452,7 @@ test('Open PR drawer does not prune saved PR context on repo switch before save'
452452
'knightedcodemonkey/css': ['stable', 'release/1.x'],
453453
})
454454

455-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
455+
await waitForAppReady(page, `${appEntryPath}`)
456456

457457
await page
458458
.getByRole('textbox', { name: 'GitHub token' })
@@ -571,7 +571,7 @@ test('Active PR context updates controls and can be closed from AI controls', as
571571
},
572572
)
573573

574-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
574+
await waitForAppReady(page, `${appEntryPath}`)
575575

576576
await page.evaluate(() => {
577577
localStorage.setItem(
@@ -661,7 +661,7 @@ test('Active PR context is disabled on load when pull request is closed', async
661661
},
662662
)
663663

664-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
664+
await waitForAppReady(page, `${appEntryPath}`)
665665

666666
await page.evaluate(() => {
667667
localStorage.setItem(
@@ -759,7 +759,7 @@ test('Active PR context rehydrates after token remove and re-add', async ({ page
759759
},
760760
)
761761

762-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
762+
await waitForAppReady(page, `${appEntryPath}`)
763763

764764
await page.evaluate(() => {
765765
localStorage.setItem('knighted:develop:github-repository', 'knightedcodemonkey/css')
@@ -870,7 +870,7 @@ test('Active PR context deactivates after token remove and re-add when PR is clo
870870
},
871871
)
872872

873-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
873+
await waitForAppReady(page, `${appEntryPath}`)
874874

875875
await page.evaluate(() => {
876876
localStorage.setItem('knighted:develop:github-repository', 'knightedcodemonkey/css')
@@ -985,7 +985,7 @@ test('Active PR context recovers when saved head branch is missing but PR metada
985985
},
986986
)
987987

988-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
988+
await waitForAppReady(page, `${appEntryPath}`)
989989

990990
await page.evaluate(() => {
991991
localStorage.setItem(
@@ -1130,7 +1130,7 @@ test('Active PR context uses Push commit flow without creating a new pull reques
11301130
},
11311131
)
11321132

1133-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
1133+
await waitForAppReady(page, `${appEntryPath}`)
11341134

11351135
await page.evaluate(() => {
11361136
localStorage.setItem(
@@ -1327,7 +1327,7 @@ test('Reloaded active PR context from URL metadata keeps Push mode and status re
13271327
},
13281328
)
13291329

1330-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
1330+
await waitForAppReady(page, `${appEntryPath}`)
13311331

13321332
await page.evaluate(() => {
13331333
localStorage.setItem(
@@ -1474,7 +1474,7 @@ test('Reloaded active PR context syncs editor content from GitHub branch and res
14741474
},
14751475
)
14761476

1477-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
1477+
await waitForAppReady(page, `${appEntryPath}`)
14781478

14791479
await page.evaluate(() => {
14801480
localStorage.setItem(
@@ -1559,7 +1559,7 @@ test('Reloaded active PR context falls back to css style mode for unsupported va
15591559
},
15601560
)
15611561

1562-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
1562+
await waitForAppReady(page, `${appEntryPath}`)
15631563

15641564
await page.evaluate(() => {
15651565
localStorage.setItem(
@@ -1585,7 +1585,7 @@ test('Reloaded active PR context falls back to css style mode for unsupported va
15851585
})
15861586

15871587
test('Open PR drawer validates unsafe filepaths', async ({ page }) => {
1588-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
1588+
await waitForAppReady(page, `${appEntryPath}`)
15891589
await connectByotWithSingleRepo(page)
15901590
await ensureOpenPrDrawerOpen(page)
15911591

@@ -1605,7 +1605,7 @@ test('Open PR drawer validates unsafe filepaths', async ({ page }) => {
16051605
test('Open PR drawer allows dotted file segments that are not traversal', async ({
16061606
page,
16071607
}) => {
1608-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
1608+
await waitForAppReady(page, `${appEntryPath}`)
16091609
await connectByotWithSingleRepo(page)
16101610
await ensureOpenPrDrawerOpen(page)
16111611

@@ -1626,7 +1626,7 @@ test('Open PR drawer allows dotted file segments that are not traversal', async
16261626
})
16271627

16281628
test('Open PR drawer rejects trailing slash file paths', async ({ page }) => {
1629-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
1629+
await waitForAppReady(page, `${appEntryPath}`)
16301630
await connectByotWithSingleRepo(page)
16311631
await ensureOpenPrDrawerOpen(page)
16321632

@@ -1645,7 +1645,7 @@ test('Open PR drawer rejects trailing slash file paths', async ({ page }) => {
16451645
test('Open PR drawer include App wrapper checkbox defaults off and resets on reopen', async ({
16461646
page,
16471647
}) => {
1648-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
1648+
await waitForAppReady(page, `${appEntryPath}`)
16491649
await connectByotWithSingleRepo(page)
16501650
await ensureOpenPrDrawerOpen(page)
16511651

@@ -1755,7 +1755,7 @@ test('Open PR drawer strips App wrapper from committed component source by defau
17551755
},
17561756
)
17571757

1758-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
1758+
await waitForAppReady(page, `${appEntryPath}`)
17591759
await connectByotWithSingleRepo(page)
17601760

17611761
const componentSource = [
@@ -1880,7 +1880,7 @@ test('Open PR drawer includes App wrapper in committed source when toggled on',
18801880
},
18811881
)
18821882

1883-
await waitForAppReady(page, `${appEntryPath}?feature-ai=true`)
1883+
await waitForAppReady(page, `${appEntryPath}`)
18841884
await connectByotWithSingleRepo(page)
18851885

18861886
await setComponentEditorSource(

src/app.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
import { createCodeMirrorEditor } from './modules/editor-codemirror.js'
88
import { defaultCss, defaultJsx, defaultReactJsx } from './modules/defaults.js'
99
import { createDiagnosticsUiController } from './modules/diagnostics-ui.js'
10-
import { isAiAssistantFeatureEnabled } from './modules/feature-flags.js'
1110
import { createGitHubChatDrawer } from './modules/github-chat-drawer/drawer.js'
1211
import { createGitHubByotControls } from './modules/github-byot-controls.js'
1312
import {
@@ -120,7 +119,6 @@ let suppressEditorChangeSideEffects = false
120119
let hasAppliedReactModeDefault = false
121120
let appToastDismissTimer = null
122121
const clipboardSupported = Boolean(navigator.clipboard?.writeText)
123-
const aiAssistantFeatureEnabled = isAiAssistantFeatureEnabled()
124122
const githubPrOpenIcon = {
125123
viewBox: '0 0 16 16',
126124
path: 'M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25 0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251 2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1 0-.354ZM3.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm8.25.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Z',
@@ -652,12 +650,7 @@ const syncAiChatTokenVisibility = token => {
652650
const hasToken = typeof token === 'string' && token.trim().length > 0
653651

654652
if (hasToken) {
655-
if (aiAssistantFeatureEnabled) {
656-
aiChatToggle?.removeAttribute('hidden')
657-
} else {
658-
aiChatToggle?.setAttribute('hidden', '')
659-
aiChatToggle?.setAttribute('aria-expanded', 'false')
660-
}
653+
aiChatToggle?.removeAttribute('hidden')
661654

662655
githubPrToggle?.removeAttribute('hidden')
663656

@@ -760,7 +753,6 @@ const prEditorSyncController = createGitHubPrEditorSyncController({
760753
})
761754

762755
chatDrawerController = createGitHubChatDrawer({
763-
featureEnabled: aiAssistantFeatureEnabled,
764756
toggleButton: aiChatToggle,
765757
drawer: aiChatDrawer,
766758
closeButton: aiChatClose,

0 commit comments

Comments
 (0)