Skip to content

Support repo-defined custom conventions - #71

Open
davidfou wants to merge 8 commits into
mainfrom
feat/custom-convention
Open

Support repo-defined custom conventions#71
davidfou wants to merge 8 commits into
mainfrom
feat/custom-convention

Conversation

@davidfou

Copy link
Copy Markdown
Owner

Summary

  • Repos can opt into their own conventional-comments vocabulary by committing .conventional-comments.json at HEAD. The content script fetches the file same-origin (no extra host permissions, no auth), validates it with a versioned zod schema, and falls back silently to the built-in default on 404, network errors, or schema violations.
  • Adds the JSON Schema (draft 2020-12) at public/schemas/v1/conventional-comments.schema.json so users can wire up IDE autocomplete via \"\$schema\".
  • New module: entrypoints/content/convention/ (types, schema, parseRepoUrl, fetchConvention, getConvention) with co-located vitest tests.
  • App split into a loader + ResolvedApp; Editor is now driven by labels / decorations / emptyLabel props instead of importing the built-in constants directly.
  • E2E coverage spans all 4 matrices (github-v1/v2, gitlab-v1/v2) via two new per-account repos (*-convention-valid-v3, *-convention-invalid-v3); see tests/e2e/convention.spec.ts.

Out-of-band setup

Before CI E2E goes green, create the two extra repos under each of the 4 e2e accounts (ccext-e2e-ci-convention-valid-v3 and ccext-e2e-ci-convention-invalid-v3), each with a PR/MR #1 and a .conventional-comments.json at HEAD — valid one declares labels spotted + approved, decoration blocker, defaultLabel: "spotted"; invalid one is e.g. {"version": 1, "labels": [], "decorations": []}.

Test plan

  • mise run lint
  • mise exec -- pnpm vitest run (153 tests, including 41 new in entrypoints/content/convention/)
  • mise run test:components (27 Playwright CT)
  • mise run build + mise run build:firefox
  • mise run test:e2e — pending the 8 setup repos above

🤖 Generated with Claude Code

davidfou and others added 8 commits June 21, 2026 19:01
Repos can opt into their own conventional-comments vocabulary by
committing `.conventional-comments.json` at HEAD. The content script
fetches the file same-origin (no extra host permissions, no auth),
validates it with a versioned zod schema, and falls back silently to
the built-in default on 404, network errors, or schema violations.

Adds the JSON Schema (draft 2020-12) at public/schemas/v1/ so users get
IDE autocomplete by setting "$schema". E2E coverage spans the existing
4 matrices via two new per-account repos (convention-valid /
convention-invalid).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The E2E `globalSetup` already creates each account's test repo on first
run via Octokit / Gitbeaker. Extends both platforms' setup to also
bootstrap the convention-valid and convention-invalid repos (with their
`.conventional-comments.json` files committed at HEAD), so a fresh
account doesn't need any manual repo prep.

Shared fixtures live in tests/e2e/conventionFixtures.ts so the spec and
the setup agree on the same vocabulary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three CI failures from the first run:
- auth.setup timed out after 30s because globalSetup now creates 3
  repos serially. Parallelize the bootstraps and bump the setup
  timeout to 120s as a safety net.
- convention.spec.ts beforeEach cleared localStorage before any
  page.goto, triggering a cross-origin SecurityError on about:blank.
  Each test navigates fresh; drop the beforeEach.
- gitlab navigation specs failed because App returned null while
  convention loaded, leaving the ccext-container with no dimensions
  exactly when the visibility probe fired. Seed useConvention with
  DEFAULT_CONVENTION so the editor renders immediately; rekey
  ResolvedApp on convention swap so its state re-initializes with
  the resolved vocabulary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Private repos (the default for new GitHub/GitLab repos, including our
E2E test accounts) return 404 to anonymous requests for raw files, so
the convention fetch silently fell back to the built-in default. Use
`credentials: "include"` so the user's existing same-origin session
cookie authenticates the read.

Safe: the request is same-origin (github.com / gitlab.com), so no
cross-site cookie leakage. This also makes the feature usable for any
private repo where the user is logged in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The same-origin fetch from the content script uses `credentials:
"include"`, but GitHub's `/raw/HEAD/` for a private repo redirects to
`raw.githubusercontent.com`, whose response lacks CORS headers for
github.com, so the body can't be read. The convention then silently
falls back to the default and the spec fails on `data-testlabel:
"praise"` instead of `"spotted"`.

Bootstrap any repo that ships a `.conventional-comments.json` as
public; keep the no-convention repo private (matches what real teams
typically have, and the fetcher returns 404 → default either way).
Bumps the convention repo names to `v4` so the existing `v3` private
repos don't shadow the new bootstrap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
github.com/{o}/{r}/raw/HEAD 302-redirects to raw.githubusercontent.com,
which returns no Access-Control-Allow-Origin header — a content-script
fetch on github.com can't read it, so every convention silently fell
back to the default and the spec failed on "praise" vs "spotted".

Switch to the platform's content API:
- GitHub: api.github.com/repos/{o}/{r}/contents/<file> with the
  `application/vnd.github.raw` Accept header. ACAO: *, so the
  cross-origin read works. credentials omitted (api.github.com uses
  bearer tokens, not session cookies).
- GitLab: gitlab.com/api/v4/projects/{encoded}/repository/files/{encoded}/raw
  (same-origin, credentials included so private repos work via the
  user's session cookie).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ResolvedApp was previously remounted via a `key` when the convention
swapped from default to custom, but the textarea still carried the
prior mount's prefix (`**praise:** `). The new initializer's
extractComment couldn't parse it under the custom vocabulary, so it
fell through to EMPTY_LABEL and the editor showed "none" instead of
"spotted" — exactly what the valid-convention spec asserted against.

Revert useConvention to a null-while-pending pattern and only mount
ResolvedApp once the final convention is in hand. A `min-h-px`
placeholder keeps the wrapper visible during the fetch so the
navigation specs don't see a zero-dimension container.

Validated locally: convention.spec (4/4) and navigation+edit (18/18
plus 1 skip) green on github-v2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous null-loader / min-h-px placeholder regressed gitlab
navigation specs (container ended up hidden between Preview→Write)
and the github-v1 visual baseline (extra 1px layout shift).

Render ResolvedApp immediately with DEFAULT_CONVENTION, then watch
the convention prop in a useEffect: when it changes, push the new
defaultLabel via setState. useTextareaWrapper's existing slice-based
effect already swaps the textarea prefix correctly on the next
render — no remount, no direct textarea mutation, no extra DOM
layer.

We don't re-run extractComment against the post-swap textarea: it
still carries the old convention's prefix at that point, so the
match would always fail and the label would collapse to EMPTY_LABEL
("none"). Trusting the new convention's defaultLabel is correct for
the new-comment case the spec exercises.

Validated locally on github-v2: convention (4/4), navigation (8/9 +
1 skip), edit (6/6).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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