diff --git a/.github/actions/start-preview-server/action.yml b/.github/actions/start-preview-server/action.yml
new file mode 100644
index 0000000..ecc34f2
--- /dev/null
+++ b/.github/actions/start-preview-server/action.yml
@@ -0,0 +1,51 @@
+# Reusable composite action — Start VitePress preview server
+#
+# Starts `bun run docs:preview` in the background and polls until the server
+# responds (or fails after a configurable timeout). Reused by a11y.yaml,
+# responsive.yaml, and any future workflow that needs a running docs preview.
+#
+# Usage:
+# - name: Start VitePress preview server
+# uses: ./.github/actions/start-preview-server
+# # (optionally) with:
+# # port: "4173"
+# # base: "/github-code-search/"
+# # timeout: "60"
+name: "Start VitePress preview server"
+description: >
+ Starts `bun run docs:preview` in the background on a given port and polls
+ until the server responds — or fails after a configurable timeout.
+
+inputs:
+ port:
+ description: TCP port the preview server should listen on.
+ default: "4173"
+ base:
+ description: Base URL path to poll when checking readiness.
+ default: "/github-code-search/"
+ timeout:
+ description: Maximum number of seconds to wait for the server to be ready.
+ default: "60"
+
+runs:
+ using: composite
+ steps:
+ - name: Start VitePress preview
+ shell: bash
+ run: bun run docs:preview -- --port ${{ inputs.port }} &
+
+ - name: Wait for preview server to be ready
+ shell: bash
+ run: |
+ URL="http://localhost:${{ inputs.port }}${{ inputs.base }}"
+ echo "Waiting for ${URL} …"
+ DEADLINE=$(( $(date +%s) + ${{ inputs.timeout }} ))
+ until curl -sf "${URL}" > /dev/null 2>&1; do
+ if [ "$(date +%s)" -ge "${DEADLINE}" ]; then
+ echo "::error::Preview server did not respond within ${{ inputs.timeout }} s on ${URL}."
+ exit 1
+ fi
+ echo "… retrying in 2 s"
+ sleep 2
+ done
+ echo "Preview server is ready."
diff --git a/.github/instructions/bug-fixing.instructions.md b/.github/instructions/bug-fixing.instructions.md
index 3771171..49d6961 100644
--- a/.github/instructions/bug-fixing.instructions.md
+++ b/.github/instructions/bug-fixing.instructions.md
@@ -5,6 +5,8 @@ excludeAgent: "code-review"
# Bug fixing — instructions for Copilot coding agent
+> **Skill reference:** for the extended symptom → module diagnostic table, test-first patterns and minimal fix principles read `.github/skills/bug-fixing.md` first.
+
Follow these steps when fixing a bug in this repository.
## 1. Reproduce the bug before writing any fix
diff --git a/.github/instructions/documentation.instructions.md b/.github/instructions/documentation.instructions.md
index 198e0fc..8ec039e 100644
--- a/.github/instructions/documentation.instructions.md
+++ b/.github/instructions/documentation.instructions.md
@@ -4,6 +4,8 @@ applyTo: "docs/**"
# Documentation — instructions for Copilot coding agent
+> **Skill reference:** for deep domain knowledge (VitePress theme architecture, CSS variables, accessibility patterns, responsive patterns, full validation commands) read `.github/skills/documentation.md` first.
+
Follow these conventions when writing or editing pages in the `docs/` directory.
## 1. Tool & rendering pipeline
@@ -93,9 +95,20 @@ docs/
Before opening a PR for any docs change:
```bash
-bun run docs:build # must complete without errors
-bun run format:check # oxfmt — no formatting diff
+bun run docs:build # must complete without errors or dead-link warnings
+bun run format:check # oxfmt — no formatting diff
```
- All internal links must resolve (VitePress reports dead links on build).
- No new `bun run knip` violations (docs/\*\* is excluded but `package.json` changes are not).
+
+If the PR modifies any Vue component, CSS, or page layout, also run the accessibility and responsive suites:
+
+```bash
+bun run docs:build:a11y
+bun run docs:preview -- --port 4173 &
+bun run docs:a11y # pa11y-ci WCAG 2.1 AA — must report 0 errors
+bun run docs:test:responsive # Playwright — 20/20 tests green (4 viewports × 5 pages)
+```
+
+See `.github/skills/documentation.md` for pa11y configuration details, WCAG contrast rules, accessible component patterns, and responsive breakpoint guidance.
diff --git a/.github/instructions/github-actions.instructions.md b/.github/instructions/github-actions.instructions.md
new file mode 100644
index 0000000..cb46890
--- /dev/null
+++ b/.github/instructions/github-actions.instructions.md
@@ -0,0 +1,145 @@
+---
+applyTo: ".github/workflows/**"
+---
+
+# GitHub Actions — instructions for Copilot coding agent
+
+> **Skill reference:** for the full best-practices guide, action-pinning decision table, composite action patterns and PR comment strategies read `.github/skills/github-actions.md` first.
+
+Follow these steps when creating or modifying GitHub Actions workflows in this repository.
+
+## 1. File conventions
+
+- All workflow files use the **`.yaml`** extension (not `.yml`).
+- File names use **kebab-case**: `a11y.yaml`, `lighthouse.yaml`, `docs.yaml`.
+- Every workflow must start with a block comment explaining its purpose, triggers and strategy.
+
+## 2. Pinning third-party actions
+
+**Every third-party action (anything not `actions/*` from GitHub itself) MUST be pinned
+to an exact commit SHA, never to a tag or a branch.**
+
+```yaml
+# ✅ correct — SHA-pinned with tag comment
+uses: owner/repo@<40-char-sha> # v1.2.3
+
+# ❌ wrong — mutable tag
+uses: owner/repo@v1
+
+# ❌ wrong — branch (can be force-pushed)
+uses: owner/repo@main
+```
+
+To resolve a tag to its commit SHA:
+
+```bash
+git ls-remote https://github.com//.git refs/tags/ refs/tags/^{}
+# The ^{} peeled SHA (second line, if present) is the actual commit. Use that one.
+```
+
+Add a comment `# v1.2.3` to the right of the hash so the version is human-readable.
+
+**First-party GitHub actions** (`actions/checkout`, `actions/upload-artifact`, etc.) should
+also be pinned. Always resolve the latest released tag before using any action.
+
+## 3. Coordinated version upgrades
+
+When bumping an action version, update **every workflow file** that uses it in the same commit.
+Actions that appear in multiple workflows (e.g. `actions/checkout`, `oven-sh/setup-bun`) must
+always reference **the same commit SHA** across all files. Use `grep` to find all usages before
+opening a PR.
+
+```bash
+grep -r "oven-sh/setup-bun" .github/workflows/
+```
+
+## 4. DRY — composite action for shared steps
+
+If two or more workflows share identical steps (e.g. starting a preview server, waiting for it
+to be ready), extract them into a **composite action** under `.github/actions//action.yml`.
+
+```yaml
+# In a workflow:
+- name: Start VitePress preview server
+ uses: ./.github/actions/start-preview-server
+```
+
+The composite action must declare all inputs with defaults and document each one. Never
+duplicate multi-step shell sequences across workflows.
+
+## 5. PR comment reports
+
+Every workflow that produces a structured report (audit scores, coverage, test results) **must**:
+
+1. Generate a formatted Markdown summary (table preferred).
+2. Post it as a **sticky PR comment** using
+ `marocchino/sticky-pull-request-comment@ # v2.9.4` with a unique `header:` value
+ so the comment is updated (not duplicated) on each push to the PR.
+3. Use `actions/github-script` to build the comment body when the data comes from JSON files
+ produced by the workflow (avoids shelling out to `jq` / Python).
+
+```yaml
+- name: Generate scores comment
+ id: comment-body
+ if: github.event_name == 'pull_request'
+ uses: actions/github-script@ # v7
+ with:
+ result-encoding: string
+ script: |
+ // ... parse JSON, build Markdown table, return body
+
+- name: Post / update comment on PR
+ if: github.event_name == 'pull_request'
+ uses: marocchino/sticky-pull-request-comment@ # v2.9.4
+ with:
+ header:
+ message: ${{ steps.comment-body.outputs.result }}
+```
+
+## 6. Minimum required permissions
+
+Set `permissions: contents: read` at the **workflow level** as the default.
+Override to `write` **at the job level** only for the exact permissions needed:
+
+```yaml
+permissions:
+ contents: read # workflow-level default (principle of least privilege)
+
+jobs:
+ audit:
+ permissions:
+ contents: read
+ pull-requests: write # needed only to post the PR comment
+```
+
+## 7. Concurrency
+
+Every workflow that produces side-effects (deploy, comment, upload) must define a
+`concurrency` block to cancel stale runs:
+
+```yaml
+concurrency:
+ group: -${{ github.ref }}
+ cancel-in-progress: true
+```
+
+## 8. Validation checklist before opening a PR
+
+```bash
+# Lint all workflow files locally (requires actionlint):
+actionlint .github/workflows/*.yaml .github/actions/**/*.yml
+
+# Confirm every action is pinned:
+grep -rn "uses:" .github/workflows/ | grep -v "@[a-f0-9]\{40\}"
+# → must return nothing
+```
+
+Also verify:
+
+- [ ] All `.yml` workflow files have been renamed to `.yaml`
+- [ ] Every new third-party action has a `# vX.Y.Z` comment after its SHA
+- [ ] New reusable composite actions are under `.github/actions//action.yml`
+- [ ] The workflow self-references the correct filename (e.g. in `paths:`)
+- [ ] `pull-requests: write` is set only on jobs that post comments
+- [ ] No `bunx` in workflow `run:` steps — use binaries from `node_modules/.bin/` or
+ scripts defined in `package.json` via `bun run
@@ -30,17 +82,26 @@ const ROWS: Row[] = [
org-wide code audits and interactive triage.
+
+ Feature comparison between gh search code and github-code-search
+
- |
-
+ | |
+
|
-
+ |
|
@@ -48,14 +109,31 @@ const ROWS: Row[] = [
- | {{ row.feature }} |
+
+
+ {{ row.feature }}
+ {{ row.desc }}
+
+
+ {{ row.feature }}
+ {{ row.desc }}
+
+ |
- ✓
- ✗
+ ✓
+ ✗
|
- ✓
- ✗
+ ✓
+ ✗
|
@@ -109,7 +187,7 @@ const ROWS: Row[] = [
.ct-intro {
margin: 0;
padding: 20px 24px 18px;
- font-size: 14px;
+ font-size: 15.5px;
line-height: 1.7;
color: var(--vp-c-text-2);
border-bottom: 1px solid var(--vp-c-divider);
@@ -123,6 +201,13 @@ const ROWS: Row[] = [
border-radius: 4px;
}
+/* Fix: brand-1 (#9933ff) on yellow brand-soft blended with card-bg = ~4.2:1 (fails)
+ * brand-2 (#7a1fd4) on white bg = 7.0:1 ✓ WCAG AA (light mode only) */
+html:not(.dark) .ct-intro code {
+ color: var(--vp-c-brand-2);
+ background: rgba(122, 31, 212, 0.08);
+}
+
.ct-intro a {
color: var(--vp-c-brand-1);
text-decoration: underline;
@@ -160,13 +245,14 @@ thead tr {
}
.ct-tool-name {
- font-size: 13px;
+ font-size: 14px;
font-weight: 600;
white-space: nowrap;
}
.ct-tool-alt {
- color: var(--vp-c-text-3);
+ /* Fix: var(--vp-c-text-3) = 2.87:1, below WCAG AA 4.5:1. text-2 ≥ 5.4:1. */
+ color: var(--vp-c-text-2);
}
.ct-tool-brand {
@@ -178,7 +264,8 @@ thead tr {
padding: 2px 8px;
border-radius: 9999px;
background: rgba(153, 51, 255, 0.12);
- color: var(--vp-c-brand-1);
+ /* Fix: brand-1 (#9933ff) on soft purple bg = ~3.6:1 → brand-2 (#7a1fd4) = 5.2:1 ✓ WCAG AA */
+ color: var(--vp-c-brand-2);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.04em;
@@ -210,12 +297,46 @@ thead tr {
}
.ct-feature {
- padding: 14px 24px;
- font-size: 14px;
+ padding: 12px 24px;
+ font-size: 15px;
color: var(--vp-c-text-1);
line-height: 1.5;
}
+.ct-feature-link,
+.ct-feature-plain {
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+ color: var(--vp-c-text-1);
+ text-decoration: none;
+}
+
+.ct-feature-link:hover {
+ color: var(--vp-c-text-1);
+}
+
+.ct-feature-title {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ font-size: 15px;
+ font-weight: 600;
+ color: inherit;
+}
+
+.ct-feature-desc {
+ font-size: 13px;
+ font-weight: 400;
+ /* Fix: var(--vp-c-text-3) ≈ 2.87:1, below WCAG AA. text-1 ensures ≥4.5:1. */
+ color: var(--vp-c-text-1);
+ line-height: 1.45;
+}
+
+.ct-feature-link:hover .ct-feature-desc {
+ color: var(--vp-c-text-2);
+}
+
.ct-cell {
padding: 14px 12px;
text-align: center;
@@ -245,29 +366,68 @@ thead tr {
color: #ef4444;
}
+/* Short/long label switching — long shown by default, short on mobile */
+.ct-name-short {
+ display: none;
+}
+
/* ── Responsive ────────────────────────────────────────────────────────── */
+
+/*
+ * ≤ 640 px — keep both tool columns but use abbreviated headers, drop badges
+ * and descriptions, tighten padding.
+ * Inspired by bun.sh's feature comparison table: 3 columns (feature + 2 tools)
+ * always visible, even on 360 px — tool headers very short, icons centred.
+ */
@media (max-width: 640px) {
+ /* Switch to abbreviated column headers */
+ .ct-name-long {
+ display: none;
+ }
+ .ct-name-short {
+ display: inline;
+ }
+
+ /* Feature col narrower, tool cols equal */
.ct-th-feature {
- width: 55%;
- padding: 14px 14px;
+ width: 56%;
+ padding: 10px 12px;
}
.ct-th-tool {
- width: 22.5%;
- padding: 14px 8px;
+ width: 22%;
+ padding: 10px 4px;
}
.ct-feature {
- padding: 12px 14px;
+ padding: 10px 12px;
font-size: 13px;
}
+ .ct-cell {
+ padding: 10px 4px;
+ }
+
.ct-tool-name {
font-size: 11px;
+ font-weight: 700;
}
.ct-badge {
display: none;
}
+
+ /* Hide descriptions — feature title alone is sufficient at small sizes */
+ .ct-feature-desc {
+ display: none;
+ }
+
+ /* Slightly smaller icons so they fit the narrow cells */
+ .ct-check,
+ .ct-cross {
+ width: 22px;
+ height: 22px;
+ font-size: 11px;
+ }
}
diff --git a/docs/.vitepress/theme/HowItWorks.vue b/docs/.vitepress/theme/HowItWorks.vue
index b14884f..a42498b 100644
--- a/docs/.vitepress/theme/HowItWorks.vue
+++ b/docs/.vitepress/theme/HowItWorks.vue
@@ -1,7 +1,7 @@
-
+
@@ -229,17 +229,17 @@
.hiw-step-badge {
display: inline-block;
- font-size: 11px;
+ font-size: 12px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--vp-c-brand-1);
- margin-bottom: 4px;
+ margin-bottom: 5px;
}
.hiw-step-title {
- margin: 0 0 6px;
- font-size: 17px;
+ margin: 0 0 8px;
+ font-size: 18px;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--vp-c-text-1);
@@ -249,8 +249,8 @@
.hiw-step-desc {
margin: 0;
- font-size: 14px;
- line-height: 1.7;
+ font-size: 15px;
+ line-height: 1.75;
color: var(--vp-c-text-2);
}
@@ -284,11 +284,11 @@ kbd {
}
.hiw-step-title {
- font-size: 15px;
+ font-size: 16px;
}
.hiw-step-desc {
- font-size: 13px;
+ font-size: 14px;
}
}
diff --git a/docs/.vitepress/theme/InstallSection.vue b/docs/.vitepress/theme/InstallSection.vue
index 2a5a364..118a988 100644
--- a/docs/.vitepress/theme/InstallSection.vue
+++ b/docs/.vitepress/theme/InstallSection.vue
@@ -1,5 +1,6 @@
-