Skip to content

feat(create-issue): stage project custom fields in native Create modal - #53

Merged
fathiraz merged 9 commits into
mainfrom
feature/create-issue-custom-fields
Jul 24, 2026
Merged

feat(create-issue): stage project custom fields in native Create modal#53
fathiraz merged 9 commits into
mainfrom
feature/create-issue-custom-fields

Conversation

@fathiraz

@fathiraz fathiraz commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

  • Injects a "Fields" chip into GitHub's native Create new issue modal (opened from a Projects v2 board), letting users stage the project's own custom fields (Status, Priority, Sprint/Iteration, number, date, text) before creation.
  • GitHub's native modal and Create button stay fully intact - title/body/assignees/labels/milestone remain native.
  • After the issue is created, an SPA-aware watcher detects the new board row (diffing item ids, no network interception) and applies the staged field values through the extension's existing bulkUpdate message pipeline - inheriting the sequential queue, rate-limit handling, and progress UI unchanged.
  • New files: src/lib/create-issue-fields-store.ts (pub/sub store), src/features/create-issue-field-flyout.tsx (chip + anchored flyout), src/features/create-issue-injections.tsx (SPA-aware injector + capture watcher).
  • Modified: src/entries/content.ts (wires the new injector into main()/ctx.onInvalidated, +3 lines).
  • No changes to background handlers, GraphQL mutations, messages.ts, or bulk-actions code - bulkUpdate already does everything the apply step needs.

Test plan

  • pnpm typecheck - clean
  • pnpm lint - no new errors (47 pre-existing warnings, unrelated)
  • pnpm test - existing suite green, including new create-issue-fields-store.test.ts
  • Manual QA against a live board (read-only selector check first - do not click Create during selector checks):
    • "Fields" chip appears in the Create new issue modal's metadata row
    • Flyout lists the project's custom fields and each editor works; badge count updates
    • Flyout is interactive inside the modal despite GitHub's focus trap
    • Creating the issue fills in staged fields on the new row (~1s later, QueueTracker shows progress)
    • "Create more" toggle re-applies staged values to each subsequent item

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

    • Flyout lists editable project fields with value pickers; badge counts only values that can apply.
    • Intercepts native Create and ⌘/Ctrl+Enter, then runs create → attach → per‑field updates; dialog assignees/labels resolved and passed through; the create mutation is not auto‑retried.
  • Bug Fixes

    • Assignees/labels: resolve selected values beyond the first 20 by querying each selection, so nothing gets dropped.
    • More reliable create: retry item ID resolution, pause on idempotent read rate limits, clear title/body, safely dismiss discard‑confirm, and re‑clear the omnibar only if its text didn’t change.
    • Schema/dev: add createIssueWithFields, restore strong typing for duplicateItem.plan, unify field‑update shapes, derive protocol types from schemas for @webext-core/messaging with a compile‑time key‑set assertion, and make the dev start URL project‑agnostic via WXT_DEV_START_URL.
    • Errors: surface the underlying GraphQL cause message so failures show a clear reason.

Written for commit bf4acb7. Summary will update on new commits.

Review in cubic


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: createIssueFieldsStore plus tests; modal UX fixes (clear title/body, dismiss discard confirm, re-clear omnibar after async board re-render); withRateLimitRetry now loops up to two rate-limit pauses with an explicit “idempotent reads only” note; bulkUpdate retries item ID resolution when a new project item isn’t visible yet; message schemas add createIssueWithFields, tighten duplicateItem.plan, and share bulk field-update shapes; minor shadow UI alignment for inline chips and WXT_DEV_START_URL for dev start URLs.

Reviewed by Cursor Bugbot for commit 6642088. Bugbot is set up for automated code reviews on this repo. Configure here.

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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 5 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/features/create-issue-injections.tsx
Comment thread src/features/create-issue-injections.tsx Outdated
Comment thread src/features/create-issue-injections.tsx Outdated
Comment thread src/features/create-issue-field-flyout.tsx Outdated
Intercept the native Create button and create/attach/apply project
fields from GraphQL responses instead of racing the board DOM.
cursor[bot]
cursor Bot approved these changes Jul 21, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 9 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/background/create-issue.ts
fathiraz added 3 commits July 21, 2026 13:23
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.
cursor[bot]
cursor Bot approved these changes Jul 22, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/features/create-issue-injections.tsx Outdated
* Each key `K` becomes `(data: SchemaInput<Messages[K]>) => SchemaOutput<Messages[K]>`,
* matching the function-signature syntax expected by `@webext-core/messaging`.
*/
export type ProtocolMapFromSchemas = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment thread src/features/create-issue-injections.tsx
fathiraz added 2 commits July 22, 2026 09:22
- 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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/background/create-issue.ts
Repository owner deleted a comment from ecc-tools Bot Jul 23, 2026
Repository owner deleted a comment from ecc-tools Bot Jul 23, 2026
Repository owner deleted a comment from cursor Bot Jul 23, 2026
fathiraz added 2 commits July 24, 2026 14:19
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.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Risk: medium. Cursor Bugbot was not present on this sync, so that signal was skipped; CI build passed. Approving this medium-risk feature change with no configured automated review findings requiring human attention. No reviewers assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver

@fathiraz
fathiraz merged commit e3cd674 into main Jul 24, 2026
5 checks passed
@fathiraz
fathiraz deleted the feature/create-issue-custom-fields branch July 24, 2026 10:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant