feat(create-issue): stage project custom fields in native Create modal - #53
Conversation
Adds a "Fields" chip to GitHub's native Create new issue modal on a Projects v2 board. Users stage the project's custom fields (Status, Priority, Sprint/Iteration, number, date, text) before creating the issue; once GitHub creates it and the new row appears on the board, the staged values are applied via the existing bulkUpdate pipeline.
There was a problem hiding this comment.
Cursor auto review
Found 2 actionable issue(s) on changed lines.
Closing the Create modal after a successful create tears down the capture watcher and clears staged fields before the new board row can be matched, so custom fields often never apply (silent failure when Create more is off).
Generated automatically when this PR was submitted using Cursor CLI with --model auto.
| currentUi = null | ||
| } | ||
|
|
||
| function teardownForDialog(): void { |
There was a problem hiding this comment.
teardownForDialog() always calls stopWatcher(), nulls watchState, and clearAll()s the store. After Create (without Create more) GitHub unmounts the dialog, so check() hits this path and aborts the in-flight capture before checkForNewRow can run—staged fields are dropped with no apply and no toast. Skip watcher/store teardown while watchState is active (only unmount the chip / clear currentDialog), and let finishWatch own cleanup.
| if (currentDialog) teardownForDialog() | ||
| currentDialog = dialog | ||
| void mountChip(dialog) | ||
| } else if (!dialog && currentDialog) { |
There was a problem hiding this comment.
This branch is what triggers the race on the default Create path: dialog leaves the DOM → teardownForDialog() while a watch is pending. Gate it on !watchState (or defer teardown until after finishWatch) so modal dismissal does not cancel a capture that was just started from the Create click.
There was a problem hiding this comment.
All reported issues were addressed across 5 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Intercept the native Create button and create/attach/apply project fields from GraphQL responses instead of racing the board DOM.
There was a problem hiding this comment.
All reported issues were addressed across 9 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
1. createIssueWithFields was missing entirely from the Messages record (35 ProtocolMap keys vs 34 schema entries). Added with correct input/output shapes matching the ProtocolMap definition. 2. duplicateItem.plan was downgraded to Schema.Unknown, losing all type safety for the 7-section DuplicateItemPlan structure. Replaced with a proper DuplicateItemPlan schema struct. Also refactored bulkUpdate to use shared BulkUpdateFieldUpdate, FieldMetaValue, and BulkUpdateDispatchResult structs (deduplication).
ProtocolMapFromSchemas maps each schema entry to a function signature (data: Input) => Output, matching the shape @webext-core/messaging expects. MessageInput<K> and MessageOutput<K> provide per-key type aliases for call-site convenience. Updated module header to document schemas as single source of truth.
…firm, re-clear omnibar GitHub's own dirty-form guard was popping a native "Discard changes?" confirmation when RGP's Close click ran on an unsaved title/body, leaving the dialog stuck open. Clear title+body before close (or before staging the next issue on Create more), poll for and click through the discard confirmation as a fallback, and re-clear the board's inline omnibar input that gets restored once the new item's async create/attach/field-update chain lands and re-renders the row.
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/lib/schemas-messages.ts">
<violation number="1" location="src/lib/schemas-messages.ts:663">
P2: Message contracts remain defined by the hand-written `ProtocolMap`, so these schema-derived types do not prevent drift or make schemas authoritative as documented. Wire `defineExtensionMessaging` and exported call-site types to `ProtocolMapFromSchemas`, then remove the duplicate interface.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| * Each key `K` becomes `(data: SchemaInput<Messages[K]>) => SchemaOutput<Messages[K]>`, | ||
| * matching the function-signature syntax expected by `@webext-core/messaging`. | ||
| */ | ||
| export type ProtocolMapFromSchemas = { |
There was a problem hiding this comment.
P2: Message contracts remain defined by the hand-written ProtocolMap, so these schema-derived types do not prevent drift or make schemas authoritative as documented. Wire defineExtensionMessaging and exported call-site types to ProtocolMapFromSchemas, then remove the duplicate interface.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/lib/schemas-messages.ts, line 663:
<comment>Message contracts remain defined by the hand-written `ProtocolMap`, so these schema-derived types do not prevent drift or make schemas authoritative as documented. Wire `defineExtensionMessaging` and exported call-site types to `ProtocolMapFromSchemas`, then remove the duplicate interface.</comment>
<file context>
@@ -592,3 +644,31 @@ export const Messages = {
+ * Each key `K` becomes `(data: SchemaInput<Messages[K]>) => SchemaOutput<Messages[K]>`,
+ * matching the function-signature syntax expected by `@webext-core/messaging`.
+ */
+export type ProtocolMapFromSchemas = {
+ [K in keyof typeof Messages]: (
+ data: SchemaInput<(typeof Messages)[K]>,
</file context>
- wxt.config.ts: startUrls now reads WXT_DEV_START_URL env var instead of a hardcoded org board - github-project.test.ts: replace kitabisa org fixture with octocat placeholder
- gate the fields flyout badge/store on canApply so a staged-then-cleared value (or an unselectable radio) no longer overcounts - carry the native dialog's assignees/labels into the create payload and resolve them to ids before createIssue, instead of silently dropping them - require the discard-confirmation dialog's text to match /discard/i before clicking its danger button, so an unrelated confirmation is never dismissed - value-gate the omnibar re-clear during the post-create watch window so a freshly-typed draft isn't wiped - add a compile-time key-set assertion between the hand-written ProtocolMap and the schema-derived ProtocolMapFromSchemas so the two can't drift
There was a problem hiding this comment.
All reported issues were addressed across 5 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
resolveAssigneeIds/resolveLabelIds queried GET_REPO_ASSIGNEES/GET_REPO_LABELS with an empty filter, so only the repo's first 20 results were available for matching selected logins/names. Query per selected value with it as the search filter instead, so selections outside that page resolve correctly. Also derives the ProtocolMap message-contract type from the schemas-messages.ts Schema record instead of a hand-written duplicate, so payload/return shapes can't drift from the schemas. Adds DeepMutable to normalize the schema-derived (readonly) types to the mutable shape @webext-core/messaging expects.


Summary
Test plan
Summary by cubic
Adds a Fields chip to GitHub’s Create Issue modal and takes over submit to create, attach to the project, and apply staged custom fields via GraphQL with progress and safe rate‑limit handling — no DOM diff. Assignees and labels picked in the modal are preserved.
New Features
Bug Fixes
createIssueWithFields, restore strong typing forduplicateItem.plan, unify field‑update shapes, derive protocol types from schemas for@webext-core/messagingwith a compile‑time key‑set assertion, and make the dev start URL project‑agnostic viaWXT_DEV_START_URL.Written for commit bf4acb7. Summary will update on new commits.
Note
Medium Risk
Replaces GitHub’s native create path for intercepted submits and issues GraphQL mutations (create is intentionally not retried; attach/field updates can still fail or rate-limit). DOM coupling to GitHub’s modal selectors adds breakage risk if the UI changes.
Overview
Adds a Fields chip and flyout on GitHub’s native Create new issue modal so users can stage project custom fields before submit. The content script intercepts the native Create button (and ⌘/Ctrl+Enter), then sends
createIssueWithFields; the new background handler runs create issue → attach to project → apply staged fields via GraphQL, using queue/progress UI and not auto-retrying the create mutation to avoid duplicates.Supporting changes:
createIssueFieldsStoreplus tests; modal UX fixes (clear title/body, dismiss discard confirm, re-clear omnibar after async board re-render);withRateLimitRetrynow loops up to two rate-limit pauses with an explicit “idempotent reads only” note;bulkUpdateretries item ID resolution when a new project item isn’t visible yet; message schemas addcreateIssueWithFields, tightenduplicateItem.plan, and share bulk field-update shapes; minor shadow UI alignment for inline chips andWXT_DEV_START_URLfor dev start URLs.Reviewed by Cursor Bugbot for commit 6642088. Bugbot is set up for automated code reviews on this repo. Configure here.