diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index ff8d47f..3e4da99 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,17 +1,28 @@ name: Playwright on: - push: + pull_request: branches: - main - paths: + types: + - opened + - synchronize + paths: &playwright_paths - 'src/**' - 'playwright/**' - 'package-lock.json' - 'playwright.config.ts' - '.github/workflows/playwright.yml' + push: + branches: + - main + paths: *playwright_paths workflow_dispatch: +concurrency: + group: playwright-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: e2e: name: ${{ matrix.jobName }} diff --git a/playwright/github-pr-drawer.spec.ts b/playwright/github-pr-drawer.spec.ts index a0af39c..224baae 100644 --- a/playwright/github-pr-drawer.spec.ts +++ b/playwright/github-pr-drawer.spec.ts @@ -14,6 +14,8 @@ import { waitForAppReady, } from './helpers/app-test-helpers.js' +const defaultCommitMessage = 'chore: sync editor updates from @knighted/develop' + const decodeGitHubFileBodyContent = (body: Record) => { const encoded = typeof body.content === 'string' ? body.content : '' return Buffer.from(encoded, 'base64').toString('utf8') @@ -80,6 +82,7 @@ const expectOpenPrConfirmationPrompt = async (page: Page) => { test('Open PR drawer confirms and submits component/styles filepaths', async ({ page, }) => { + const customCommitMessage = 'chore: sync develop editor outputs' let createdRefBody: CreateRefRequestBody | null = null const upsertRequests: Array<{ path: string; body: Record }> = [] let pullRequestBody: PullRequestCreateBody | null = null @@ -184,6 +187,7 @@ test('Open PR drawer confirms and submits component/styles filepaths', async ({ await page .getByLabel('PR description') .fill('Generated from editor content in @knighted/develop.') + await page.getByLabel('Commit message').fill(customCommitMessage) await submitOpenPrAndConfirm(page, { expectedSummaryLines: [ @@ -208,6 +212,8 @@ test('Open PR drawer confirms and submits component/styles filepaths', async ({ expect(upsertRequests).toHaveLength(2) expect(upsertRequests[0]?.path).toBe('examples/component/App.tsx') expect(upsertRequests[1]?.path).toBe('examples/styles/app.css') + expect(upsertRequests[0]?.body.message).toBe(customCommitMessage) + expect(upsertRequests[1]?.body.message).toBe(customCommitMessage) expect(pullRequestPayload?.head).toBe('Develop/Open-Pr-Test') expect(pullRequestPayload?.base).toBe('main') @@ -221,9 +227,9 @@ test('Open PR drawer confirms and submits component/styles filepaths', async ({ await expect(page.getByLabel('PR title')).toHaveValue( 'Apply editor updates from develop', ) - await expect(page.getByLabel('PR description')).toHaveValue( - 'Generated from editor content in @knighted/develop.', - ) + await expect(page.getByLabel('PR description')).toBeHidden() + await expect(page.getByLabel('Commit message')).toBeVisible() + await expect(page.getByLabel('Commit message')).toHaveValue(customCommitMessage) await expect( page.getByRole('button', { name: 'Push commit to active pull request branch' }), ).toBeVisible() @@ -232,6 +238,19 @@ test('Open PR drawer confirms and submits component/styles filepaths', async ({ ).toBeVisible() }) +test('Open PR drawer starts with empty title/description and short default head', async ({ + page, +}) => { + await waitForAppReady(page, `${appEntryPath}?feature-ai=true`) + await connectByotWithSingleRepo(page) + await ensureOpenPrDrawerOpen(page) + + const headValue = await page.getByLabel('Head').inputValue() + expect(headValue).toMatch(/^feat\/component-[a-z0-9]{4}$/) + await expect(page.getByLabel('PR title')).toHaveValue('') + await expect(page.getByLabel('PR description')).toHaveValue('') +}) + test('Open PR drawer base dropdown updates from mocked repo branches', async ({ page, }) => { @@ -678,6 +697,79 @@ test('Active PR context is disabled on load when pull request is closed', async expect(isActivePr).toBe(false) }) +test('Active PR context recovers when saved head branch is missing but PR metadata exists', async ({ + page, +}) => { + await page.route('https://api.github.com/user/repos**', async route => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify([ + { + id: 11, + owner: { login: 'knightedcodemonkey' }, + name: 'develop', + full_name: 'knightedcodemonkey/develop', + default_branch: 'main', + permissions: { push: true }, + }, + ]), + }) + }) + + await mockRepositoryBranches(page, { + 'knightedcodemonkey/develop': ['main', 'release', 'develop/open-pr-test'], + }) + + await page.route( + 'https://api.github.com/repos/knightedcodemonkey/develop/pulls/2', + async route => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + number: 2, + state: 'open', + title: 'Recovered PR context title', + html_url: 'https://github.com/knightedcodemonkey/develop/pull/2', + head: { ref: 'develop/open-pr-test' }, + base: { ref: 'main' }, + }), + }) + }, + ) + + await waitForAppReady(page, `${appEntryPath}?feature-ai=true`) + + await page.evaluate(() => { + localStorage.setItem( + 'knighted:develop:github-pr-config:knightedcodemonkey/develop', + JSON.stringify({ + componentFilePath: 'examples/component/App.tsx', + stylesFilePath: 'examples/styles/app.css', + renderMode: 'react', + baseBranch: 'main', + headBranch: '', + prTitle: 'Recovered PR context title', + prBody: 'Saved body', + isActivePr: true, + pullRequestNumber: 2, + pullRequestUrl: 'https://github.com/knightedcodemonkey/develop/pull/2', + }), + ) + }) + + await connectByotWithSingleRepo(page) + + await expect( + page.getByRole('button', { name: 'Push commit to active pull request branch' }), + ).toBeVisible() + + await ensureOpenPrDrawerOpen(page) + await expect(page.getByRole('button', { name: 'Push commit' }).last()).toBeVisible() + await expect(page.getByLabel('Head')).toHaveValue('develop/open-pr-test') +}) + test('Active PR context uses Push commit flow without creating a new pull request', async ({ page, }) => { @@ -815,8 +907,34 @@ test('Active PR context uses Push commit flow without creating a new pull reques await connectByotWithSingleRepo(page) await ensureOpenPrDrawerOpen(page) + await expect(page.getByLabel('Pull request repository')).toBeDisabled() + await expect(page.getByLabel('Pull request base branch')).toBeDisabled() + await expect(page.getByLabel('Head')).toHaveJSProperty('readOnly', true) + await expect(page.getByLabel('Component filename')).toHaveJSProperty('readOnly', true) + await expect(page.getByLabel('Styles filename')).toHaveJSProperty('readOnly', true) + await expect(page.getByLabel('PR title')).toHaveJSProperty('readOnly', true) + await expect( + page.getByLabel('Include App wrapper in committed component source'), + ).toBeEnabled() + await expect(page.getByLabel('Commit message')).toBeEditable() + + await expect(page.getByLabel('PR description')).toBeHidden() + await expect(page.getByLabel('Commit message')).toBeVisible() + + const includeWrapperToggle = page.getByLabel( + 'Include App wrapper in committed component source', + ) + await expect(includeWrapperToggle).toBeEnabled() + await includeWrapperToggle.check() + await expect(includeWrapperToggle).toBeChecked() + await expect(page.getByRole('button', { name: 'Push commit' }).last()).toBeVisible() + await expect(page.getByLabel('PR description')).toBeHidden() + await expect(page.getByLabel('Commit message')).toBeVisible() + await setComponentEditorSource(page, 'const commitMarker = 1') await setStylesEditorSource(page, '.commit-marker { color: red; }') + const pushCommitMessage = 'chore: push active context sync' + await page.getByLabel('Commit message').fill(pushCommitMessage) await page.getByRole('button', { name: 'Push commit' }).last().click() @@ -846,6 +964,8 @@ test('Active PR context uses Push commit flow without creating a new pull reques expect(upsertRequests).toHaveLength(2) expect(upsertRequests[0]?.path).toBe('examples/component/App.tsx') expect(upsertRequests[1]?.path).toBe('examples/styles/app.css') + expect(upsertRequests[0]?.body.message).toBe(pushCommitMessage) + expect(upsertRequests[1]?.body.message).toBe(pushCommitMessage) }) test('Reloaded active PR context from URL metadata keeps Push mode and status reference', async ({ @@ -988,6 +1108,8 @@ test('Reloaded active PR context from URL metadata keeps Push mode and status re await ensureOpenPrDrawerOpen(page) await expect(page.getByRole('button', { name: 'Push commit' }).last()).toBeVisible() await expect(page.getByLabel('Head')).toHaveValue('develop/open-pr-test') + await expect(page.getByLabel('PR description')).toBeHidden() + await expect(page.getByLabel('Commit message')).toBeVisible() await setComponentEditorSource(page, 'const commitMarker = 1') await setStylesEditorSource(page, '.commit-marker { color: red; }') @@ -1007,6 +1129,8 @@ test('Reloaded active PR context from URL metadata keeps Push mode and status re expect(upsertRequests).toHaveLength(2) expect(upsertRequests[0]?.path).toBe('examples/component/App.tsx') expect(upsertRequests[1]?.path).toBe('examples/styles/app.css') + expect(upsertRequests[0]?.body.message).toBe(defaultCommitMessage) + expect(upsertRequests[1]?.body.message).toBe(defaultCommitMessage) }) test('Reloaded active PR context syncs editor content from GitHub branch', async ({ @@ -1151,6 +1275,7 @@ test('Open PR drawer validates unsafe filepaths', async ({ page }) => { await ensureOpenPrDrawerOpen(page) const componentPath = page.getByLabel('Component filename') + await page.getByLabel('PR title').fill('Validate unsafe paths') await componentPath.fill('../outside/App.tsx') await expect(componentPath).toHaveValue('../outside/App.tsx') await componentPath.blur() @@ -1176,6 +1301,7 @@ test('Open PR drawer allows dotted file segments that are not traversal', async await stylesPath.fill('styles/foo..bar.css') await expect(componentPath).toHaveValue('docs/v1.0..v1.1/App.tsx') await expect(stylesPath).toHaveValue('styles/foo..bar.css') + await page.getByLabel('PR title').fill('Allow dotted file segments') await stylesPath.blur() await expectOpenPrConfirmationPrompt(page) @@ -1190,6 +1316,7 @@ test('Open PR drawer rejects trailing slash file paths', async ({ page }) => { await ensureOpenPrDrawerOpen(page) await page.getByLabel('Component filename').fill('src/components/') + await page.getByLabel('PR title').fill('Reject trailing slash path') await clickOpenPrDrawerSubmit(page) await expect( @@ -1325,6 +1452,7 @@ test('Open PR drawer strips App wrapper from committed component source by defau await ensureOpenPrDrawerOpen(page) await page.getByLabel('Head').fill('develop/repo/editor-sync-without-app') + await page.getByLabel('PR title').fill('Strip App wrapper by default') await submitOpenPrAndConfirm(page) await expect( @@ -1455,6 +1583,7 @@ test('Open PR drawer includes App wrapper in committed source when toggled on', await includeWrapperToggle.check() await page.getByLabel('Head').fill('develop/repo/editor-sync-with-app') + await page.getByLabel('PR title').fill('Include App wrapper in commit') await submitOpenPrAndConfirm(page) await expect( diff --git a/playwright/helpers/app-test-helpers.ts b/playwright/helpers/app-test-helpers.ts index 593ab73..5af299b 100644 --- a/playwright/helpers/app-test-helpers.ts +++ b/playwright/helpers/app-test-helpers.ts @@ -33,10 +33,19 @@ export type BranchesByRepo = Record export const waitForAppReady = async (page: Page, path = appEntryPath) => { await page.goto(path) await expect(page.getByRole('heading', { name: '@knighted/develop' })).toBeVisible() - await expect(page.locator('#cdn-loading')).toHaveAttribute('hidden', '') await expect - .poll(() => page.getByRole('status', { name: 'App status' }).textContent()) - .not.toBe('Idle') + .poll(async () => { + const statusText = ( + await page.getByRole('status', { name: 'App status' }).textContent() + )?.trim() + + return ( + statusText === 'Rendered' || + statusText?.startsWith('Rendered (Type errors:') || + statusText === 'Error' + ) + }) + .toBe(true) } export const waitForInitialRender = async (page: Page) => { @@ -215,7 +224,8 @@ export const connectByotWithSingleRepo = async (page: Page) => { await page.getByRole('button', { name: 'Add GitHub token' }).click() const repoSelect = page.getByLabel('Pull request repository') - await expect(repoSelect).toBeEnabled({ timeout: 60_000 }) + await expect(repoSelect).toHaveValue('knightedcodemonkey/develop') + await expect(repoSelect).toHaveValue('knightedcodemonkey/develop') await expect( diff --git a/src/app.js b/src/app.js index aebd9b0..6cb7d22 100644 --- a/src/app.js +++ b/src/app.js @@ -60,6 +60,7 @@ const githubPrComponentPath = document.getElementById('github-pr-component-path' const githubPrStylesPath = document.getElementById('github-pr-styles-path') const githubPrTitle = document.getElementById('github-pr-title') const githubPrBody = document.getElementById('github-pr-body') +const githubPrCommitMessage = document.getElementById('github-pr-commit-message') const githubPrIncludeAppWrapper = document.getElementById('github-pr-include-app-wrapper') const githubPrSubmit = document.getElementById('github-pr-submit') const componentPrSyncIcon = document.getElementById('component-pr-sync-icon') @@ -801,6 +802,7 @@ prDrawerController = createGitHubPrDrawer({ stylesPathInput: githubPrStylesPath, prTitleInput: githubPrTitle, prBodyInput: githubPrBody, + commitMessageInput: githubPrCommitMessage, includeAppWrapperToggle: githubPrIncludeAppWrapper, submitButton: githubPrSubmit, titleNode: openPrTitle, diff --git a/src/index.html b/src/index.html index cf4fd13..0bd037b 100644 --- a/src/index.html +++ b/src/index.html @@ -798,7 +798,7 @@

Open Pull Request

type="text" autocomplete="off" spellcheck="false" - placeholder="develop/repo/editor-sync-yyyymmdd" + placeholder="feat/component-ab12" /> @@ -857,6 +857,20 @@

Open Pull Request

placeholder="Describe the generated editor updates." > + +
@@ -881,7 +895,7 @@

Open Pull Request

-

Loading playground assets…

+

Loading IDE assets…

Fetching runtimes and compilers from CDN. This can take a moment.

diff --git a/src/modules/github-pr-drawer.js b/src/modules/github-pr-drawer.js index 264bf56..2ad158f 100644 --- a/src/modules/github-pr-drawer.js +++ b/src/modules/github-pr-drawer.js @@ -22,6 +22,8 @@ const defaultPrConfig = { stylesFilePath: 'src/styles/app.css', } +const defaultCommitMessage = 'chore: sync editor updates from @knighted/develop' + const supportedRenderModes = new Set(['dom', 'react']) const normalizeRenderMode = value => { @@ -202,22 +204,6 @@ const sanitizeBranchPart = value => { .replace(/^[-/.]+|[-/.]+$/g, '') } -const sanitizeAutoBranchPart = value => sanitizeBranchPart(value).toLowerCase() - -const toUtcBranchStamp = () => { - const now = new Date() - const parts = [ - String(now.getUTCFullYear()), - String(now.getUTCMonth() + 1).padStart(2, '0'), - String(now.getUTCDate()).padStart(2, '0'), - String(now.getUTCHours()).padStart(2, '0'), - String(now.getUTCMinutes()).padStart(2, '0'), - String(now.getUTCSeconds()).padStart(2, '0'), - ] - - return `${parts[0]}${parts[1]}${parts[2]}-${parts[3]}${parts[4]}${parts[5]}` -} - const createBranchEntropySuffix = () => Math.random().toString(36).slice(2, 6) const isAutoGeneratedHeadBranch = value => { @@ -226,30 +212,12 @@ const isAutoGeneratedHeadBranch = value => { return false } - return /^develop\/[^/]+\/editor-sync-\d{8}(?:-\d{6})?(?:-[a-z0-9]{4})?(?:-\d+)?$/.test( - branch, - ) + return /^feat\/component-[a-z0-9]{4}(?:-\d+)?$/.test(branch) } -const createDefaultBranchName = repository => { - const repoName = sanitizeAutoBranchPart(repository?.name ?? '') || 'repo' - const stamp = toUtcBranchStamp() +const createDefaultBranchName = () => { const entropy = createBranchEntropySuffix() - return `develop/${repoName}/editor-sync-${stamp}-${entropy}` -} - -const toDefaultPrTitle = repository => { - const label = toSafeText(repository?.fullName) || 'selected repository' - return `Apply component and styles edits to ${label}` -} - -const toDefaultPrBody = ({ componentFilePath, stylesFilePath }) => { - return [ - 'This PR was created from @knighted/develop editor content.', - '', - `- Component source -> ${componentFilePath}`, - `- Styles source -> ${stylesFilePath}`, - ].join('\n') + return `feat/component-${entropy}` } const buildSummary = ({ @@ -259,6 +227,7 @@ const buildSummary = ({ componentFilePath, stylesFilePath, prTitle, + commitMessage, actionType, }) => { const repositoryLabel = toSafeText(repository?.fullName) || 'No repository selected' @@ -270,6 +239,7 @@ const buildSummary = ({ `Component file path: ${componentFilePath}`, `Styles file path: ${stylesFilePath}`, `PR title: ${prTitle}`, + `Commit message: ${commitMessage}`, ] if (!isPushCommit) { @@ -412,6 +382,7 @@ export const createGitHubPrDrawer = ({ stylesPathInput, prTitleInput, prBodyInput, + commitMessageInput, includeAppWrapperToggle, submitButton, titleNode, @@ -476,6 +447,64 @@ export const createGitHubPrDrawer = ({ return getActiveRepositoryPrContext(repositoryFullName) } + const syncModeFields = () => { + const isPushCommitMode = Boolean(getCurrentActivePrContext()) + + if (repositorySelect instanceof HTMLSelectElement) { + repositorySelect.disabled = submitting || isPushCommitMode + } + + if (baseBranchInput instanceof HTMLSelectElement) { + baseBranchInput.disabled = submitting || isPushCommitMode + } + + if (baseBranchInput instanceof HTMLInputElement) { + baseBranchInput.readOnly = isPushCommitMode + baseBranchInput.disabled = submitting + } + + if (headBranchInput instanceof HTMLInputElement) { + headBranchInput.readOnly = isPushCommitMode + headBranchInput.disabled = submitting + } + + if (componentPathInput instanceof HTMLInputElement) { + componentPathInput.readOnly = isPushCommitMode + componentPathInput.disabled = submitting + } + + if (stylesPathInput instanceof HTMLInputElement) { + stylesPathInput.readOnly = isPushCommitMode + stylesPathInput.disabled = submitting + } + + if (prTitleInput instanceof HTMLInputElement) { + prTitleInput.required = !isPushCommitMode + prTitleInput.readOnly = isPushCommitMode + prTitleInput.disabled = submitting + } + + const prBodyField = prBodyInput?.closest('.github-pr-field') + if (prBodyField instanceof HTMLElement) { + prBodyField.hidden = isPushCommitMode + } + + if (prBodyInput instanceof HTMLTextAreaElement) { + prBodyInput.required = false + prBodyInput.disabled = submitting || isPushCommitMode + } + + if (includeAppWrapperToggle instanceof HTMLInputElement) { + includeAppWrapperToggle.disabled = submitting + } + + if (commitMessageInput instanceof HTMLInputElement) { + commitMessageInput.required = false + commitMessageInput.readOnly = false + commitMessageInput.disabled = submitting + } + } + const setSubmitButtonLabel = ({ isPending = false } = {}) => { if (!(submitButton instanceof HTMLButtonElement)) { return @@ -484,11 +513,16 @@ export const createGitHubPrDrawer = ({ const activeContext = getCurrentActivePrContext() const isPushCommitMode = Boolean(activeContext) + if (drawer instanceof HTMLElement) { + drawer.dataset.mode = isPushCommitMode ? 'push' : 'open' + } + if (isPending) { submitButton.textContent = isPushCommitMode ? 'Pushing commit...' : 'Opening PR...' if (titleNode instanceof HTMLElement) { titleNode.textContent = isPushCommitMode ? 'Push Commit' : 'Open Pull Request' } + syncModeFields() return } @@ -497,6 +531,8 @@ export const createGitHubPrDrawer = ({ if (titleNode instanceof HTMLElement) { titleNode.textContent = isPushCommitMode ? 'Push Commit' : 'Open Pull Request' } + + syncModeFields() } const emitRenderModeRestore = activeContext => { @@ -549,6 +585,7 @@ export const createGitHubPrDrawer = ({ stylesPathInput, prTitleInput, prBodyInput, + commitMessageInput, includeAppWrapperToggle, ]) { if ( @@ -559,6 +596,8 @@ export const createGitHubPrDrawer = ({ input.disabled = isPending } } + + syncModeFields() } const getFormValues = () => { @@ -569,6 +608,7 @@ export const createGitHubPrDrawer = ({ stylesFilePath: normalizeFilePath(stylesPathInput?.value), prTitle: toSafeText(prTitleInput?.value), prBody: typeof prBodyInput?.value === 'string' ? prBodyInput.value.trim() : '', + commitMessage: toSafeText(commitMessageInput?.value), } } @@ -659,16 +699,16 @@ export const createGitHubPrDrawer = ({ return } - const headBranch = sanitizeBranchPart(savedConfig.headBranch) - if (!headBranch) { - return - } - const pullRequestNumberFromConfig = typeof savedConfig.pullRequestNumber === 'number' && Number.isFinite(savedConfig.pullRequestNumber) ? savedConfig.pullRequestNumber : parsePullRequestNumberFromUrl(savedConfig.pullRequestUrl) + const headBranch = sanitizeBranchPart(savedConfig.headBranch) + + if (!pullRequestNumberFromConfig && !headBranch) { + return + } abortPendingContextVerifyRequest() const abortController = new AbortController() @@ -711,18 +751,26 @@ export const createGitHubPrDrawer = ({ } if (resolvedPullRequest?.isOpen) { + const nextHeadBranch = + sanitizeBranchPart(resolvedPullRequest.headRef) || headBranch + const nextBaseBranch = + toSafeText(resolvedPullRequest.baseRef) || toSafeText(savedConfig.baseBranch) + saveRepositoryPrConfig({ repositoryFullName, config: { ...savedConfig, isActivePr: true, renderMode: normalizeRenderMode(savedConfig.renderMode), + headBranch: nextHeadBranch, + baseBranch: nextBaseBranch, pullRequestNumber: resolvedPullRequest.number, pullRequestUrl: resolvedPullRequest.htmlUrl, prTitle: toSafeText(savedConfig.prTitle) || toSafeText(resolvedPullRequest.title), }, }) + syncFormForRepository({ resetBranch: true }) setSubmitButtonLabel() emitActivePrContextChange() void syncActivePrEditorContent() @@ -793,8 +841,10 @@ export const createGitHubPrDrawer = ({ }), ) + const isPushCommitMode = Boolean(getCurrentActivePrContext()) + baseBranchInput.replaceChildren(...options) - baseBranchInput.disabled = submitting + baseBranchInput.disabled = submitting || isPushCommitMode baseBranchInput.value = baseBranch } @@ -972,23 +1022,26 @@ export const createGitHubPrDrawer = ({ headBranchInput.value = savedHeadBranch && !isAutoGeneratedHeadBranch(savedHeadBranch) ? savedHeadBranch - : createDefaultBranchName(repository) + : createDefaultBranchName() } } if (prTitleInput instanceof HTMLInputElement) { if (resetAll || repositoryChanged || !toSafeText(prTitleInput.value)) { - prTitleInput.value = - toSafeText(savedDraftConfig.prTitle) || toDefaultPrTitle(repository) + prTitleInput.value = toSafeText(savedDraftConfig.prTitle) } } if (prBodyInput instanceof HTMLTextAreaElement) { if (resetAll || repositoryChanged || !toSafeText(prBodyInput.value)) { prBodyInput.value = - typeof savedDraftConfig.prBody === 'string' && savedDraftConfig.prBody - ? savedDraftConfig.prBody - : toDefaultPrBody({ componentFilePath, stylesFilePath }) + typeof savedDraftConfig.prBody === 'string' ? savedDraftConfig.prBody : '' + } + } + + if (commitMessageInput instanceof HTMLInputElement) { + if (resetAll || repositoryChanged || !toSafeText(commitMessageInput.value)) { + commitMessageInput.value = '' } } @@ -1009,6 +1062,32 @@ export const createGitHubPrDrawer = ({ const values = getFormValues() const currentRenderMode = normalizeRenderMode(getRenderMode?.()) const existingConfig = readRepositoryPrConfig(repositoryFullName) + const isActivePr = existingConfig?.isActivePr === true + + if (isActivePr) { + saveRepositoryPrConfig({ + repositoryFullName, + config: { + ...existingConfig, + componentFilePath: + typeof existingConfig?.componentFilePath === 'string' + ? existingConfig.componentFilePath + : values.componentFilePath, + stylesFilePath: + typeof existingConfig?.stylesFilePath === 'string' + ? existingConfig.stylesFilePath + : values.stylesFilePath, + renderMode: currentRenderMode, + isActivePr: true, + pullRequestNumber: existingConfig?.pullRequestNumber, + pullRequestUrl: existingConfig?.pullRequestUrl, + }, + }) + + setSubmitButtonLabel() + emitActivePrContextChange() + return + } saveRepositoryPrConfig({ repositoryFullName, @@ -1020,7 +1099,7 @@ export const createGitHubPrDrawer = ({ prTitle: values.prTitle, prBody: values.prBody, renderMode: currentRenderMode, - isActivePr: existingConfig?.isActivePr === true, + isActivePr: false, pullRequestNumber: existingConfig?.pullRequestNumber, pullRequestUrl: existingConfig?.pullRequestUrl, }, @@ -1126,6 +1205,16 @@ export const createGitHubPrDrawer = ({ const targetStylesPathValue = isPushCommitMode ? activeContext?.stylesFilePath : values.stylesFilePath + const targetCommitMessage = values.commitMessage || defaultCommitMessage + + if ( + !isPushCommitMode && + prTitleInput instanceof HTMLInputElement && + !prTitleInput.checkValidity() + ) { + prTitleInput.reportValidity() + return + } const includeAppWrapper = includeAppWrapperToggle instanceof HTMLInputElement @@ -1175,6 +1264,7 @@ export const createGitHubPrDrawer = ({ componentFilePath: componentPathValidation.value, stylesFilePath: stylesPathValidation.value, prTitle: targetPrTitle, + commitMessage: targetCommitMessage, actionType: isPushCommitMode ? 'push-commit' : 'open-pr', }) @@ -1209,7 +1299,7 @@ export const createGitHubPrDrawer = ({ componentSource, stylesFilePath: stylesPathValidation.value, stylesSource: typeof getStylesSource === 'function' ? getStylesSource() : '', - commitMessage: `chore: sync editor component and styles from @knighted/develop`, + commitMessage: targetCommitMessage, signal: abortController.signal, }) : createEditorContentPullRequest({ @@ -1223,7 +1313,7 @@ export const createGitHubPrDrawer = ({ componentSource, stylesFilePath: stylesPathValidation.value, stylesSource: typeof getStylesSource === 'function' ? getStylesSource() : '', - commitMessage: `chore: sync editor component and styles from @knighted/develop`, + commitMessage: targetCommitMessage, signal: abortController.signal, }) diff --git a/src/styles/ai-controls.css b/src/styles/ai-controls.css index 350d00d..6c89ea1 100644 --- a/src/styles/ai-controls.css +++ b/src/styles/ai-controls.css @@ -240,7 +240,6 @@ .github-repo-wrap select { appearance: none; - -webkit-appearance: none; background-color: var(--surface-select); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath fill='currentColor' d='M1.2 0 5 3.8 8.8 0 10 1.2 5 6 0 1.2z'/%3E%3C/svg%3E"); background-repeat: no-repeat; @@ -551,6 +550,10 @@ color: var(--text-muted); } +.github-pr-field[hidden] { + display: none !important; +} + .github-pr-field--checkbox { display: flex; align-items: center; @@ -588,7 +591,6 @@ .github-pr-field select { appearance: none; - -webkit-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath fill='currentColor' d='M1.2 0 5 3.8 8.8 0 10 1.2 5 6 0 1.2z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 10px center; @@ -608,6 +610,57 @@ box-shadow: 0 0 0 1px color-mix(in srgb, var(--focus-ring) 80%, transparent); } +.github-pr-field input[readonly], +.github-pr-field textarea[readonly], +.github-pr-field select:disabled, +.github-pr-field input:disabled, +.github-pr-field textarea:disabled { + background: color-mix(in srgb, var(--surface-select) 82%, var(--surface-panel)); + border-color: color-mix(in srgb, var(--border-control) 72%, var(--text-subtle)); + color: color-mix(in srgb, var(--select-text) 78%, var(--text-subtle)); + cursor: default; +} + +.github-pr-field select:disabled { + opacity: 1; +} + +.github-pr-field--checkbox input[type='checkbox']:disabled + span { + color: color-mix(in srgb, var(--text-subtle) 82%, var(--border-subtle)); +} + +.github-pr-drawer[data-mode='push'] + .github-pr-field + :is( + input[readonly], + textarea[readonly], + select:disabled, + input:disabled, + textarea:disabled + ) { + background: color-mix(in srgb, var(--surface-panel-header) 72%, var(--text-muted)); + border-color: color-mix(in srgb, var(--border-control) 64%, var(--text-muted)); + color: color-mix(in srgb, var(--select-text) 38%, var(--text-subtle)); + box-shadow: none; +} + +.github-pr-drawer[data-mode='push'] + .github-pr-field + :is(input[readonly], textarea[readonly]) { + color: color-mix(in srgb, var(--select-text) 36%, var(--text-subtle)); +} + +.github-pr-drawer[data-mode='push'] + .github-pr-field + :is(input[readonly], textarea[readonly], select:disabled)::placeholder { + color: color-mix(in srgb, var(--text-subtle) 62%, var(--border-subtle)); +} + +.github-pr-drawer[data-mode='push'] .github-pr-field label, +.github-pr-drawer[data-mode='push'] .github-pr-field > span { + color: color-mix(in srgb, var(--text-muted) 88%, var(--text-subtle)); +} + .github-pr-drawer__actions { display: flex; justify-content: flex-end; @@ -750,7 +803,6 @@ .ai-chat-model-picker select { appearance: none; - -webkit-appearance: none; width: 100%; min-width: 0; box-sizing: border-box;