diff --git a/.agents/skills/review-pr/SKILL.md b/.agents/skills/review-pr/SKILL.md index 7a25b4b2104c9..818d303a28f22 100644 --- a/.agents/skills/review-pr/SKILL.md +++ b/.agents/skills/review-pr/SKILL.md @@ -132,6 +132,80 @@ Check 3 lines above each match for a guard (`canUseDOM`, `typeof window !== 'und | `// @ts-ignore` without explanation | WARNING | | `any` type in new code | INFO | +### I. Documentation coverage + +A code change frequently lands something that **someone downstream** needs to learn about. The "someone" splits into two audiences with very different reading habits, so this check actually walks two passes: + +- **Pass 1 — user-facing docs.** Component consumers (storybook stories, migration guides, docsite, MDX, change-file comments). +- **Pass 2 — harness / agent-facing docs.** Future agent sessions reading the skills, `AGENTS.md`, `docs/workflows/*`, and `docs/architecture/*`. When a PR changes a build target, a script, a CI step, a label taxonomy, a project-board field, or an assumption that a skill makes, the skill or harness doc has to be updated **in the same PR** — otherwise every fresh `/triage-issues`, `/visual-test`, `/review-pr`, etc. invocation pays the discovery cost over again. + +Both passes ask the same two questions: should this PR have updated docs, and if so, did it? Bug fixes that restore documented behavior, internal refactors, and test-only PRs default to PASS in both passes — the interesting cases are below. + +#### Pass 1 — user-facing docs + +| Source change | Expected doc surface | +| --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | +| New prop on a public component (added in `*.types.ts`) | A Storybook story or MDX entry under `packages/react-components/react-/stories/` that exercises the prop | +| New public export added to a `library/src/index.ts` barrel | A story or MDX entry covering it (or a justified note in the PR body) | +| New component package (new directory under `packages/react-components/react-*`) | Stories package, `library/docs/Spec.md`, and an entry on the docsite (`apps/public-docsite-v9`) | +| Behavior change to a public API's defaults (e.g. a new conditional in `use*_unstable` that changes observable output) | Mention in `library/docs/MIGRATION.md` or a "BREAKING CHANGE / behavior change" section in the PR body | +| Removal or deprecation of a public export | `library/docs/MIGRATION.md` entry and/or a `@deprecated` JSDoc on the symbol | +| New design token, classname constant, or CSS custom property | A line in the relevant docs (often the component's stories MDX) explaining how to use/override it | + +#### Pass 2 — harness engineering / agent-facing docs + +This pass exists because skills are part of the contract, not just convenience. When the skills are stale, the agents are wrong — quietly. Symptoms: a `/visual-test` invocation that points at a nonexistent project name, a `/triage-issues` flow that recommends a label the repo no longer has, a `/triage-board` skill that misses a new view filter the maintainers added. + +Walk the diff for any of these and check whether the matching agent-facing doc was updated: + +| Source change | Expected harness update | +| --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| New / renamed nx target (`project.json`, `nx.json`, generators) | Any skill that invokes that target — `visual-test`, `lint-check`, `package-info`, `v9-component`. The skill literally types the target name; it's not auto-discovered. | +| New / renamed top-level `package.json` script | Same. Plus `docs/workflows/contributing.md` if it's contributor-facing. | +| New label, label rename, or label taxonomy change in the repo (`gh api repos/.../labels`) | `triage-issues/references/triage-labels.md` — the skill uses an allow-list and `gh issue edit` rejects unknown labels. | +| New / changed project-board field, view filter, or option ID | `triage-board/references/team-mapping.md` (option IDs) and the view-filter mirror in `triage-board/SKILL.md`. | +| New convention contributors are expected to follow (file layout, naming, "always do X") | `AGENTS.md` / `CLAUDE.md` (they're symlinked) — the rules section is the agent's first read. | +| New layered dependency rule, package-tier reshuffle, or CODEOWNERS rewrite | `docs/architecture/layers.md`, `docs/team-routing.md`, plus `triage-board/references/team-mapping.md` if a CODEOWNERS handle gained or lost a confident mapping. | +| New first-time-setup requirement (e.g. a workspace package whose `lib-commonjs/` must be pre-built) | `docs/workflows/contributing.md` "First-time setup" section AND the troubleshooting block of any skill that hits the failure mode (`visual-test` already does this for the unstable-deps case). | +| New skill, agent hook, or `.claude/`-level configuration | `AGENTS.md` skills table + the `.claude/skills//SKILL.md` bridge file. | +| Removal or move of a path that any skill greps for or hard-codes | The skill that referenced it. Search the skills (`grep -rn "" .agents/skills/`) before merging. | +| Authentication / token-scope / permission changes that affect `gh` calls | The skill's preflight section. Concrete past examples: the EMU vs non-EMU active-account gotcha and the `read:project` vs `project` scope distinction now baked into `triage-board`'s preflight. | +| New external-tool name or behavior change (e.g. assignee identity for an automated agent) | The relevant skill. Concrete past example: assigning to `Copilot` literally fails with "Bot does not have access" — only `copilot-swe-agent` works. That belongs in any skill that assigns to Copilot. | + +#### When docs are NOT expected (default to PASS) + +- Pure bug fix that restores documented behavior +- Internal refactor with no exported-symbol or behavior change +- Build / CI changes that have no contributor-facing or skill-facing impact +- Test-only PRs +- Style-only PRs (typo, formatting) on internal code + +#### Severity + +| Situation | Severity | +| ----------------------------------------------------------------------------------------------------------------------- | -------- | +| Public export removed or behavior-changed without `MIGRATION.md` entry | BLOCKER | +| Skill references a renamed or removed path / target / label that this PR changes (skill will literally fail next run) | BLOCKER | +| New public component / new public hook with no story or MDX | WARNING | +| New public prop with no story exercising it | WARNING | +| New design token / className constant with no usage example | WARNING | +| Behavior change to public defaults with no PR-body callout | WARNING | +| New label / project field / nx target / convention without the matching skill or `AGENTS.md` / `docs/workflows/` update | WARNING | +| New first-time-setup requirement without `contributing.md` mention | WARNING | +| PR description explicitly defers docs to a follow-up (link cited) | INFO | +| Docs / harness change exists but feels minimal — could be more thorough | INFO | + +#### How to actually run this check + +The model should walk the diff, not guess. + +1. **Group changed files into surfaces.** Code surfaces (`library/src/**`, `index.ts` barrels, `*.types.ts`); user-doc surfaces (`stories/**`, `library/docs/**`, `apps/public-docsite-v9/**`); harness surfaces (`.agents/skills/**`, `.claude/**`, `AGENTS.md`, `CLAUDE.md`, `docs/workflows/**`, `docs/architecture/**`, `docs/team-routing.md`, `project.json`, `nx.json`, root `package.json` scripts, `.github/CODEOWNERS`, `.github/labeler.yml`). +2. **If the PR is purely a doc/harness surface**, the check is PASS — it's documentation already. +3. **For each row in the user-facing table that matches a code change in the diff**, verify the corresponding doc surface was also touched. If not, raise the matching severity. +4. **For each row in the harness-engineering table that matches**, verify the corresponding skill or harness doc was also touched. Be specific: name the skill that needs updating, not just "a skill." +5. **Cross-search**: when a PR renames a path, target, or label, run `grep -rn "" .agents/skills/ docs/ AGENTS.md` to find references that will break. Anything that turns up is a BLOCKER unless this PR also updates it. +6. **Read the PR body** before reporting. Authors often pre-empt with "docs follow-up tracked in #NNNN" or "harness skills updated in commit X" — those move findings from WARNING to INFO when the deferral is explicit and reasonable. + ## Phase 4: Calculate Confidence Score ``` @@ -158,16 +232,11 @@ Score interpretation: ## Phase 5: Produce Output -Use this exact format: - -``` -## PR Review: # +Use this exact format (also used verbatim as the PR-comment body in Phase 6 — don't duplicate work). -**Author:** <author> -**Type:** <detected PR type> -**Packages affected:** <list> -**CI Status:** <passing/failing/pending> +Start directly with the score. Skip a header title, author, type, packages-affected, and CI-status preamble — when this is posted as a PR comment, all of that is already visible in the GitHub UI immediately above the comment, so repeating it just pushes the actually-useful content (score + findings) below the fold. The classification work from Phase 2 still happens; it just isn't echoed back at the reader. +``` ### Confidence Score: <score>/100 <one-sentence summary> @@ -195,6 +264,7 @@ Use this exact format: | API surface | PASS/WARN | ... | | Accessibility | PASS/WARN | ... | | Security/Quality | PASS/WARN | ... | +| Docs coverage | PASS/WARN/FAIL | ... | ### Recommendation @@ -203,6 +273,41 @@ APPROVE / REQUEST_CHANGES / COMMENT <brief rationale> ``` +## Phase 6: Post the review back to the PR + +After presenting the output in the chat, post the same text as a comment on the PR so the review is visible to maintainers and to Copilot (when the PR author is `copilot-swe-agent`, the comment becomes actionable feedback). + +Save the output from Phase 5 to a temp file so the markdown isn't mangled by shell quoting, then: + +```bash +gh pr comment $ARGUMENTS --repo microsoft/fluentui --body-file /tmp/pr-review-$ARGUMENTS.md +``` + +Append a single trailer line to the body so the post is identifiable: + +``` +--- +*Posted via the `/review-pr` skill.* +``` + +The posted comment should be **identical** to what you rendered in chat — don't paraphrase or summarize. The chat output and the PR comment must match so the user can trust that what they saw is what the maintainers see. + +**Pre-checks before posting:** + +1. Confirm the active `gh` account has write access to the PR's repo (EMU accounts read fine but silently fail on writes): + ```bash + gh api graphql -f query='{ viewer { login } repository(owner:"microsoft", name:"fluentui") { viewerPermission } }' + ``` + If `viewerPermission` is `NONE`, stop and ask the user to `gh auth switch --user <non-emu-account>`. +2. Don't post on PRs from your own branches unless explicitly asked — self-review comments are noise. +3. If `REQUEST_CHANGES` is the recommendation, still post the comment but note to the user that only a formal review (`gh pr review --request-changes`) actually blocks merge; a comment is advisory. + +**When to skip posting:** + +- The user explicitly asks for a review without posting ("review but don't post"). +- The PR has an existing comment from this skill within the last day on the same head SHA — avoid duplicate noise. Look for the `*Posted via the \`/review-pr\` skill.\*` trailer. +- Draft PRs where the user is clearly still iterating (state=OPEN, isDraft=true, recent force-push) — offer to post but don't do it by default. + ## Notes - For large PRs (50+ files), prioritize: published source files > test files > config. Note reduced confidence due to review scope. diff --git a/.agents/skills/triage-board/SKILL.md b/.agents/skills/triage-board/SKILL.md new file mode 100644 index 0000000000000..d1b112500a6b0 --- /dev/null +++ b/.agents/skills/triage-board/SKILL.md @@ -0,0 +1,196 @@ +--- +name: triage-board +description: Triage items on the Fluent UI Unified project board (org-level GitHub Project at microsoft/projects/395). Fetches open issues across microsoft/fluentui, microsoft/fluentui-system-icons, and microsoft/fluentui-contrib that currently have no `Team` field set on the board, reads CODEOWNERS for each issue's package to route it to the right team (cxe-prg / cxe-red / teams-prg / cxe-coastal / v-build / xc-uxe / fluentui-motion / …), proposes a specific GitHub assignee from the CODEOWNERS line, and applies both via `gh` after the user approves. Use this skill whenever the user asks to triage the Fluent board, route unassigned project items, assign teams to board items, process the board's New column, triage unified board, or any variation of project-board team routing — distinct from the `triage-issues` skill which handles the repo-level `Needs: Triage :mag:` queue. +allowed-tools: Bash Read Grep Glob +--- + +# Triage the Fluent Unified Board + +Your job is to walk the items on `microsoft/projects/395` (the "Fluent UI - Unified" board) that don't yet have a `Team` field set, work out which team owns each one based on `CODEOWNERS`, propose a GitHub assignee, and apply both after the user approves. + +This skill is distinct from **`triage-issues`**: + +- `triage-issues` handles repo-level issues labeled `Needs: Triage :mag:` (the Shield workflow) — adds labels, assigns area owners, sometimes validates repros, and removes the triage label. +- `triage-board` operates at the **project-board** layer — sets the `Team` single-select field on the board and (secondarily) adds a GitHub-issue assignee. It does not touch labels, does not close issues, and does not alter `Status`. + +Both skills can run in the same session without interfering, but don't conflate them. + +Operate in **recommend-then-apply** mode: never mutate anything until the user has approved the batch. CODEOWNERS can list multiple candidates per path, and in edge cases (cross-cutting issues, unclear area) a wrong auto-assignment is annoying to undo — the approval gate is worth the extra step. + +## Preflight: verify the gh account can write + +Two distinct problems can block Step 5 (apply) — both should be caught upfront so the user doesn't spend time reviewing recommendations that can't be applied: + +**1. Account identity / repo permission.** The board and its linked repos are under the `microsoft` org; EMU (Enterprise Managed User) tokens typically cannot mutate them even though they can read: + +```bash +gh api graphql -f query='{ viewer { login } repository(owner:"microsoft", name:"fluentui") { viewerPermission } }' +``` + +If `viewerPermission` is `NONE` or the active viewer is an EMU account, stop and suggest `gh auth switch --user <non-emu-account>`. + +**2. Token scope for ProjectV2 mutations.** `read:project` is enough to fetch items, but the `updateProjectV2ItemFieldValue` mutation requires the unqualified `project` scope. Check scopes directly — the read-query above will pass even when write scope is missing: + +```bash +gh auth status 2>&1 | grep -A0 "Token scopes" | grep -q "'project'," || echo "MISSING_PROJECT_SCOPE" +``` + +If the check prints `MISSING_PROJECT_SCOPE`, stop and ask the user to run `gh auth refresh -s project` (interactive device flow). Tell them to paste `! gh auth refresh -s project` into the prompt so the output lands in this session. Do not try to attempt the mutation and recover — every apply call will fail the same way and waste time. + +Failing loud on either of these is much better than failing at apply time after the user has spent time reviewing recommendations. + +## What each item needs + +For every untriaged board item, produce a recommendation with: + +| Field | Possible values | +| ---------------------- | --------------------------------------------------------------------------------------- | +| `repo` | `microsoft/fluentui` / `microsoft/fluentui-system-icons` / `microsoft/fluentui-contrib` | +| `issue_number` | the issue number in that repo | +| `team` | one of the board's Team options (see `references/team-mapping.md`) | +| `team_confidence` | `high` (clear CODEOWNERS hit), `medium` (partial match), `low` (flag for human) | +| `assignee` | GitHub login from CODEOWNERS, or `null` if only a team handle appears | +| `assignee_reason` | which CODEOWNERS line gave you the user | +| `needs_human_followup` | anything you're unsure about — surface it, don't paper over it | + +Don't invent Team values. The board's Team field has a fixed option set — see `references/team-mapping.md` for the mapping and option IDs. If an item's ownership doesn't map to any listed Team option, mark `team_confidence: low` and flag it for the user. + +## The fetch + +The board is an org-level ProjectV2 with ID `PVT_kwDOAF3p4s4AD4d_` and project number `395`. Items span three repos. Fetch everything and filter client-side — the GraphQL API doesn't let you filter on "field not set" directly. + +The full paginated query is in `references/graphql-snippets.md` under "Fetch untriaged items". Use it as-is; pagination matters because the board has 600+ items and the untriaged subset changes over time. + +After fetch, filter to items where ALL of these hold (this matches the board's **"By team"** view — view 6 — which is the canonical triage view): + +- `content.__typename === "Issue"` (skip DraftIssue and PullRequest) +- `content.state === "OPEN"` +- No `fieldValues` node has `field.name === "Team"` (i.e. Team is unset) +- No `fieldValues` node has `field.name === "Status"` and value `"✅ Done"` +- Labels do NOT include any of: `Help Wanted ✨`, `Type: Epic`, `Needs: Triage :mag:`, `Resolution: Soft Close` + +The label exclusions matter: + +- `Needs: Triage :mag:` means the repo-level Shield queue hasn't cleared the issue yet — the `triage-issues` skill handles those first. Assigning a board Team to a repo-level-untriaged issue is premature. +- `Resolution: Soft Close` is applied to stale items that the inactivity bot has closed-adjacent. Routing these to a team wastes cycles on work nobody plans to do. +- `Type: Epic` and `Help Wanted ✨` are intentionally unowned categories on the board — the project owner manages those separately. + +To confirm the canonical filter hasn't drifted, sanity-check view 6's `filter` string before a big triage run: + +```bash +gh api graphql -f query='{ organization(login:"microsoft") { projectV2(number:395) { view(number:6) { filter } } } }' +``` + +## Routing logic + +### Step 1: identify the owning package/area + +For each issue, look at the **body** for the Area/Package field (our issue template has one), the **labels** (a `Package: charting` or `Component: Button` label is a strong signal), and the **title** (component names). For fluentui-contrib and fluentui-system-icons the signal set is smaller — usually the labels carry the area. + +If the issue targets a specific package path inside `fluentui`, grep CODEOWNERS for that path: + +```bash +# Example: react-combobox +grep -n 'react-combobox' /Users/doidor/src/microsoft/fluentui/.github/CODEOWNERS | head -3 +``` + +For issues in sibling repos (`fluentui-system-icons`, `fluentui-contrib`), fetch that repo's CODEOWNERS: + +```bash +gh api repos/microsoft/<repo>/contents/.github/CODEOWNERS -H "Accept: application/vnd.github.raw" 2>/dev/null +``` + +CODEOWNERS lines look like: + +``` +packages/react-components/react-tooltip/library @microsoft/cxe-prg @mainframev +``` + +The first `@microsoft/<handle>` is the owning team; any `@<individual-user>` entries are the specific owners. + +### Step 2: map the team handle to a board Team value + +See `references/team-mapping.md` for the authoritative mapping. The confident mappings cover most traffic (`cxe-prg`, `cxe-red`, `teams-prg`, `cxe-coastal`, `v-build`, `fluentui-motion`, `xc-uxe`). Unmapped or ambiguous handles (`charting-team`, `fluentui-admins`, `fluentui-northstar`, `azure-design-engineering`, etc.) → `team_confidence: low`, flag for human. + +**Product-override rule — v9 issues never go to cxe-red.** The cxe-red team owns v8 exclusively. If an issue carries the `Fluent UI react-components (v9)` label — even alongside a v8 label, and even when CODEOWNERS resolves to `@microsoft/cxe-red` (e.g. via an older path reference) — route it to `cxe-prg` and flag it for human confirmation. The reverse is fine: v8-only issues stay on cxe-red. When both v8 and v9 labels are present on a single issue, the v9 label wins for routing purposes, since that's where active development happens. + +### Step 3: propose a specific GitHub assignee + +Prefer the individual user(s) named on the CODEOWNERS line. Rules: + +- **One individual + team handle** → propose that individual. This is the common case. +- **Multiple individuals + team handle** → propose the first individual, note the others in `needs_human_followup`. +- **Team handle only (no individual)** → leave `assignee: null` and flag. The user will assign manually since `gh issue edit --add-assignee` doesn't accept teams. +- **Existing assignee on the issue** → don't overwrite. Note the current assignee in the recommendation and only set the Team field. + +For cross-cutting issues (issue body mentions multiple components, or the author didn't pick one), default to team-only routing — flag for human rather than guess a person. + +## Workflow + +### Step 1 — Fetch + +Run the preflight auth check, then fetch all project items (paginated). Filter to the untriaged open-issue subset. Print the count and a one-line summary per item (`#<num>` / `<repo-short>` / `<title-truncated>` / current assignees if any). If the list is empty, tell the user and stop. + +### Step 2 — Classify + +For each item, go through the routing logic. Cache any CODEOWNERS file you fetch (at least per-session) so you don't re-fetch for every item in the same repo. + +Don't dump raw CODEOWNERS lines at the user — extract the team + user you care about and move on. + +### Step 3 — Present recommendations + +Show a single table the user can scan. Suggested columns: `#`, `repo-short` (e.g. `fluentui`, `contrib`, `icons`), `title` (truncated), `team`, `confidence`, `assignee`, `notes`. Group or sort by team so the user can spot clusters — it makes spot-checks faster. + +If there are low-confidence items or items needing human follow-up, call those out in a section above the table so they don't hide inside the noise. + +### Step 4 — Ask for approval + +Ask: "Apply all / apply specific numbers / skip / edit?". Accept `apply all`, `apply 36012 36016`, `skip 35998`, or free-form corrections (e.g. "for #35976 use cxe-prg instead"). Don't proceed to apply until the user responds. + +### Step 5 — Apply approved changes + +Two mutations per approved item: + +**Set the board Team field** (GraphQL mutation; `gh project item-edit` can also do this but GraphQL is more transparent): + +```bash +gh api graphql -f query=' +mutation($projectId:ID!, $itemId:ID!, $fieldId:ID!, $optionId:String!) { + updateProjectV2ItemFieldValue(input:{ + projectId: $projectId, + itemId: $itemId, + fieldId: $fieldId, + value: { singleSelectOptionId: $optionId } + }) { projectV2Item { id } } +}' \ + -f projectId="PVT_kwDOAF3p4s4AD4d_" \ + -f itemId="<PVTI_... from fetch>" \ + -f fieldId="PVTSSF_lADOAF3p4s4AD4d_zgCPFLY" \ + -f optionId="<team option id from team-mapping.md>" +``` + +**Set the GitHub-issue assignee** (only if the recommendation has a specific user, and the issue doesn't already have an assignee): + +```bash +gh issue edit <num> --repo <owner/repo> --add-assignee <login> +``` + +Apply one item at a time and print a one-line result per item. Do not retry blindly on failure — stop and ask the user what to do. If a specific item fails (e.g. the assignee user isn't in the org), skip it and continue with the rest, then surface the failed ones in the summary. + +### Step 6 — Summarize + +Print: X items triaged (team set + assignee), Y items team-only (flagged for human to pick an assignee), Z items skipped or failed (with the specific reason per item). + +## Anti-patterns + +- **Don't touch `Status`.** The user wants items to stay in `🌱 New` after triage — only the Team field changes. Setting Status is out of scope. +- **Don't touch labels** on the underlying issue. Label triage belongs to the `triage-issues` skill. +- **Don't assume a single CODEOWNERS file.** Cross-repo items need the CODEOWNERS fetched from their own repo. +- **Don't guess an assignee for team-only CODEOWNERS lines.** Better to leave `assignee: null` and let the user pick. +- **Don't overwrite existing assignees.** If the issue already has one, respect it — your job is to set the Team, not re-assign work. +- **Don't retry on auth failure.** If the mutation returns `Unauthorized: As an Enterprise Managed User, you cannot access this content`, the whole batch will fail the same way. Surface it and stop. + +## Reference files + +- `references/team-mapping.md` — CODEOWNERS handle → board Team option (name + option ID) mapping, plus the known-ambiguous handles that should trip `team_confidence: low`. +- `references/graphql-snippets.md` — fetch query for paginated untriaged items, plus the mutation for setting the Team field. diff --git a/.agents/skills/triage-board/evals/evals.json b/.agents/skills/triage-board/evals/evals.json new file mode 100644 index 0000000000000..6ab763ce29411 --- /dev/null +++ b/.agents/skills/triage-board/evals/evals.json @@ -0,0 +1,22 @@ +{ + "skill_name": "triage-board", + "evals": [ + { + "id": 1, + "prompt": "triage the fluent board", + "expected_output": "Runs preflight auth check, fetches the Unified board items, filters to open issues with no Team field set across all three linked repos, maps each via CODEOWNERS to the right board Team, proposes an assignee from the CODEOWNERS line, presents a recommendation table, waits for approval before mutating.", + "files": [] + }, + { + "id": 2, + "prompt": "can you go through the unified project board and figure out which team each untriaged item should go to?", + "expected_output": "Same flow as above. Should correctly distinguish v9 (cxe-prg) from v8 (cxe-red) from web-components (cxe-coastal), and flag any CODEOWNERS handles without a board mapping (charting-team, northstar) as low-confidence.", + "files": [] + }, + { + "id": 3, + "prompt": "route the unassigned items on microsoft/projects/395 to the right teams", + "expected_output": "Fetches items, classifies, recommends team + assignee, does not touch Status or labels. Apply only after approval. Leaves team-only CODEOWNERS lines with assignee empty (flagged for human)." + } + ] +} diff --git a/.agents/skills/triage-board/references/graphql-snippets.md b/.agents/skills/triage-board/references/graphql-snippets.md new file mode 100644 index 0000000000000..ccab196fb9403 --- /dev/null +++ b/.agents/skills/triage-board/references/graphql-snippets.md @@ -0,0 +1,195 @@ +# GraphQL snippets for the board + +All snippets assume the user is authenticated with a non-EMU token that has `read:project` (and `write:project` for mutations) plus repo-level write access to the three linked repos. Run the preflight check from `SKILL.md` first. + +## Fetch untriaged items (paginated) + +The board has 600+ items; this query pages 100 at a time. Keep calling with `after: <endCursor>` until `hasNextPage` is false, then filter client-side for open issues with no `Team` value. + +```graphql +query ($cursor: String) { + organization(login: "microsoft") { + projectV2(number: 395) { + items(first: 100, after: $cursor) { + pageInfo { + hasNextPage + endCursor + } + nodes { + id + content { + __typename + ... on Issue { + number + title + url + state + body + repository { + nameWithOwner + } + assignees(first: 5) { + nodes { + login + } + } + labels(first: 20) { + nodes { + name + } + } + } + ... on DraftIssue { + title + } + ... on PullRequest { + number + title + url + state + } + } + fieldValues(first: 30) { + nodes { + __typename + ... on ProjectV2ItemFieldSingleSelectValue { + field { + ... on ProjectV2SingleSelectField { + name + } + } + name + } + } + } + } + } + } + } +} +``` + +Minimal bash + python pagination: + +```bash +cursor="" +> /tmp/board-items.jsonl +for i in $(seq 1 20); do + if [ -z "$cursor" ]; then + resp=$(gh api graphql -f query="$(cat /tmp/board-query.graphql)") + else + resp=$(gh api graphql -f query="$(cat /tmp/board-query.graphql)" -F cursor="$cursor") + fi + echo "$resp" > /tmp/board-page.json + python3 - <<'PY' +import json +d = json.load(open('/tmp/board-page.json')) +items = d['data']['organization']['projectV2']['items'] +with open('/tmp/board-items.jsonl','a') as out: + for n in items['nodes']: + out.write(json.dumps(n) + '\n') +with open('/tmp/board-meta.json','w') as m: + json.dump({'hasNext': items['pageInfo']['hasNextPage'], + 'cursor': items['pageInfo']['endCursor']}, m) +PY + has_next=$(python3 -c "import json; print(json.load(open('/tmp/board-meta.json'))['hasNext'])") + cursor=$(python3 -c "import json; print(json.load(open('/tmp/board-meta.json'))['cursor'])") + [ "$has_next" != "True" ] && break +done +``` + +## Client-side filter: untriaged open issues (matches view 6) + +This mirrors the canonical "By team" view's filter. Keep this in sync with the view — see `SKILL.md` for how to refetch the view's filter string if it ever drifts. + +```python +import json + +EXCLUDE_LABELS = { + 'Help Wanted ✨', + 'Type: Epic', + 'Needs: Triage :mag:', + 'Resolution: Soft Close', +} + +def is_untriaged_open_issue(item): + c = item.get('content') or {} + if c.get('__typename') != 'Issue': + return False + if c.get('state') != 'OPEN': + return False + labels = {l['name'] for l in c.get('labels', {}).get('nodes', [])} + if labels & EXCLUDE_LABELS: + return False + has_team = False + status_done = False + for fv in item.get('fieldValues', {}).get('nodes', []): + f = fv.get('field') or {} + if f.get('name') == 'Team': + has_team = True + if f.get('name') == 'Status' and fv.get('name') == '✅ Done': + status_done = True + if has_team or status_done: + return False + return True + +with open('/tmp/board-items.jsonl') as f: + untriaged = [json.loads(line) for line in f if is_untriaged_open_issue(json.loads(line))] +``` + +## Set the Team field on an item + +```bash +gh api graphql -f query=' +mutation($projectId:ID!, $itemId:ID!, $fieldId:ID!, $optionId:String!) { + updateProjectV2ItemFieldValue(input:{ + projectId: $projectId, + itemId: $itemId, + fieldId: $fieldId, + value: { singleSelectOptionId: $optionId } + }) { projectV2Item { id } } +}' \ + -f projectId="PVT_kwDOAF3p4s4AD4d_" \ + -f itemId="<PVTI_...>" \ + -f fieldId="PVTSSF_lADOAF3p4s4AD4d_zgCPFLY" \ + -f optionId="<option id from team-mapping.md>" +``` + +`gh project item-edit` can do the same thing if the token has write access, but the GraphQL form above is more transparent (you see exactly which field and option you're setting) and it's one less CLI subcommand for the skill to remember. + +## Clear the Team field (undo a wrong set) + +When a mistake slips past the approval gate and needs to be undone: + +```bash +gh api graphql -f query=' +mutation($projectId:ID!, $itemId:ID!, $fieldId:ID!) { + clearProjectV2ItemFieldValue(input:{ + projectId: $projectId, + itemId: $itemId, + fieldId: $fieldId + }) { projectV2Item { id } } +}' \ + -f projectId="PVT_kwDOAF3p4s4AD4d_" \ + -f itemId="<PVTI_...>" \ + -f fieldId="PVTSSF_lADOAF3p4s4AD4d_zgCPFLY" +``` + +## Add a GitHub-issue assignee + +```bash +gh issue edit <num> --repo <owner/repo> --add-assignee <login> +``` + +Only call this when the recommendation has a specific login AND the issue has no existing assignee. If the user to assign is not a member of the repo org, the call will fail with `could not add assignee` — skip that item and move on. + +## Fetch CODEOWNERS for a sibling repo + +CODEOWNERS path may vary by repo. Most Microsoft repos keep it at `.github/CODEOWNERS`; some use `CODEOWNERS` at the root or `docs/CODEOWNERS`. Start with `.github/CODEOWNERS`: + +```bash +gh api repos/microsoft/<repo>/contents/.github/CODEOWNERS \ + -H "Accept: application/vnd.github.raw" 2>/dev/null +``` + +If that returns 404, try the other locations. Cache the result in memory for the remainder of the session — the file is typically 100–500 lines and you'll re-check it for many items. diff --git a/.agents/skills/triage-board/references/team-mapping.md b/.agents/skills/triage-board/references/team-mapping.md new file mode 100644 index 0000000000000..0a9b94583c8ec --- /dev/null +++ b/.agents/skills/triage-board/references/team-mapping.md @@ -0,0 +1,74 @@ +# CODEOWNERS handle → board Team mapping + +The "Fluent UI - Unified" board (org project `microsoft/projects/395`) has a fixed set of options for its `Team` single-select field. CODEOWNERS uses a wider set of team handles than the board tracks, so not every CODEOWNERS entry maps cleanly — the skill should flag uncertain ones for human review rather than guess. + +To refresh the option IDs against the live project (they're stable but not immortal): + +```bash +gh api graphql -f query='{ + organization(login:"microsoft") { + projectV2(number:395) { + id + fields(first:30) { + nodes { + ... on ProjectV2SingleSelectField { id name options { id name } } + } + } + } + } +}' | jq '.data.organization.projectV2 | {id, teamField: (.fields.nodes[] | select(.name=="Team"))}' +``` + +## Project + field IDs (as of this skill's authoring) + +- `projectId` = `PVT_kwDOAF3p4s4AD4d_` +- `teamFieldId` = `PVTSSF_lADOAF3p4s4AD4d_zgCPFLY` + +## Board Team options + +| Board option | Option ID | +| ----------------- | ---------- | +| `cxe-prg` | `5aacad01` | +| `cxe-red` | `d78a8f20` | +| `cxe-coastal` | `40933abb` | +| `teams-prg` | `64f7bd9e` | +| `v-a11y` | `4eda3bd1` | +| `v-build` | `82c4d92c` | +| `v-migration` | `3a22f81f` | +| `v-perf` | `15907658` | +| `v-pm` | `96a8ae0f` | +| `contributor` | `fe8e8988` | +| `xc-uxe` | `e7e0e0e0` | +| `fluentui-motion` | `207075c9` | + +## Confident mappings + +Use these for automatic routing. Evidence is either docs/team-routing.md, a well-known team purpose, or repeated usage in CODEOWNERS that corresponds to a single product area. + +| CODEOWNERS handle | Board Team | Notes | +| ------------------------------------ | ----------------- | -------------------------------------------------------------- | +| `@microsoft/cxe-prg` | `cxe-prg` | v9 component ownership (most of `packages/react-components/*`) | +| `@microsoft/cxe-red` | `cxe-red` | v8 component ownership (most of `packages/react/*`) | +| `@microsoft/teams-prg` | `teams-prg` | Teams-owned packages in fluentui + contrib | +| `@microsoft/fluentui-react-build` | `v-build` | build tooling, `tools/*`, `.github/*`, root configs | +| `@microsoft/fui-wc` | `cxe-coastal` | web components team (owns `packages/web-components/*`) | +| `@microsoft/fluent-motion-framework` | `fluentui-motion` | motion / animation primitives | +| `@microsoft/xc-uxe` | `xc-uxe` | appears in contrib for UXE-owned packages | +| `@microsoft/fluentui-react` | `cxe-red` | older v8 alias (docs/team-routing.md maps v8 to cxe-red) | + +## Ambiguous / flag-for-human + +Don't auto-route these. Set `team_confidence: low` and put a specific ask in `needs_human_followup` so the maintainer can pick. + +| CODEOWNERS handle | Why ambiguous | +| ------------------------------------- | ----------------------------------------------------------------------------------- | +| `@microsoft/charting-team` | No board Team option — charting doesn't have its own column in this project | +| `@microsoft/fluentui-northstar` | Northstar is EOL (v0); routing depends on whether the item should be closed instead | +| `@microsoft/fluentui-v` | Too generic — could be v8 or v9 | +| `@microsoft/fluentui-admins` | Meta / admin team, not a product team | +| `@microsoft/azure-design-engineering` | Azure partner team in contrib; unclear if they self-triage or roll up to xc-uxe | +| `@microsoft/cap-theme` | Contrib-only team; unclear board mapping | +| `@microsoft/ms-fabric` | Contrib-only team; unclear board mapping | +| `@microsoft/fluentui-variant-theme` | Contrib-only team; unclear board mapping | + +When you encounter an unmapped handle not in either table, treat it as ambiguous by default and surface it. The list above is expected to grow — add new confident mappings only when the user confirms them explicitly. diff --git a/.agents/skills/triage-issues/SKILL.md b/.agents/skills/triage-issues/SKILL.md new file mode 100644 index 0000000000000..a9e784f4b9999 --- /dev/null +++ b/.agents/skills/triage-issues/SKILL.md @@ -0,0 +1,260 @@ +--- +name: triage-issues +description: Triage newly-filed GitHub issues on the Fluent UI repo (microsoft/fluentui) following the Shield triage guidelines. Fetches open issues labeled `Needs: Triage :mag:` via the `gh` CLI, classifies each (bug vs feature, product area, partner ask, repro quality, a11y), recommends label changes and area-owner assignment, and then applies the approved changes. Use this skill whenever the user asks to triage issues, run shield triage, go through the triage queue, process needs-triage, or any variation — even if they don't mention "Fluent UI" or "GitHub" explicitly, since that's the project context here. +allowed-tools: Bash Read Grep Glob +--- + +# Triage Fluent UI Issues (Shield Workflow) + +Your job is to walk the current `Needs: Triage :mag:` queue on `microsoft/fluentui`, classify each issue against the Shield triage rules, and apply the right labels / assignee after the user approves. + +This skill operates in **recommend-then-apply** mode: never mutate issues until the user has approved the batch. A wrong label is cheap to add and annoying to remove, so lean on the approval step. + +## The triage queue + +```bash +gh issue list --repo microsoft/fluentui \ + --search 'is:open is:issue label:"Needs: Triage :mag:"' \ + --limit 50 \ + --json number,title,author,labels,createdAt,body,comments +``` + +Sort oldest-first — the guidelines prioritize the longest-waiting issues. + +## The decision each issue needs + +For every issue, you're producing a **recommendation** with these fields: + +| Field | Possible values | +| ---------------------- | ------------------------------------------------------------------------------------ | +| `classification` | `bug`, `feature`, `question`, `a11y`, `needs-repro`, `duplicate`, `not-an-issue` | +| `product` | `v9`, `v8`, `v7`, `web-components`, `charting`, `northstar`, `unknown` | +| `is_partner_ask` | `true` / `false` (see `references/partner-orgs.md`) | +| `priority_signal` | `p1`, `normal`, `help-wanted`, `good-first-issue` | +| `add_labels` | list of label names to add | +| `remove_labels` | list of label names to remove (always includes `Needs: Triage :mag:` once triaged) | +| `assignee` | GitHub login of the area owner (see routing table below) | +| `comment` | optional message to leave for the author (required if asking for more info) | +| `validation_candidate` | `true` / `false` — does this bug warrant a playwright-cli repro check before triage? | +| `needs_human_followup` | anything you're unsure about — surface it, don't paper over it | + +Don't invent labels. Every label you recommend must exist in the repo — see `references/triage-labels.md` for the allow-list. + +## Classification rules (from Shield guidelines) + +Read `references/shield-guidelines.md` for the full rules. The short version: + +1. **Questions** → convert to a GitHub Discussion; don't triage as an issue. +2. **Bug with clear repro**: + - Assign area owner (see routing below). + - If the report documents a critical regression (measured impact, root-cause analysis, broad blast radius) → recommend `Shield: P1` and flag `needs_human_followup` so the user can post in the Shield Teams channel. This decision is about the report, not the reporter. + - If you have private notes (via memory) indicating the author is on a tracked partner workstream AND the body references that workstream → recommend `Partner Ask` as well. When in doubt, leave the label off and flag for human verification — see `references/partner-orgs.md`. + - If product version is v9 or v8 → normal flow. v9 bugs always get resolution preference. + - If version is v7 → recommend `Resolution: Won't Fix` with a polite comment, unless the report has exceptional business impact signals that warrant an exception. +3. **Bug without a reliable repro** → recommend `Needs: Repro` + `Needs: Author Feedback`, draft a comment asking for a minimal reproduction (StackBlitz preferred). +4. **Feature request**: + - **First, check whether it's a "port v8 behavior to v9" ask.** If the body cites a v8 component/prop that v9 doesn't have, investigate before backlogging. v9 deliberately rebuilt many APIs around composition (Field wraps controls, motion lives in `react-motion-components-preview`, announcements via `useAnnounce`, etc.). There's usually a v9-native pattern already documented — see the investigation step below. + - If a v9-native pattern exists → **recommend `Resolution: By Design` + close**, with a comment pointing the reporter to the pattern. This keeps the backlog honest. + - If there's no v9 equivalent and the ask is valid for v9 → `Needs: Backlog review`. + - For older versions → likely close as out-of-support unless partner ask. +5. **Accessibility-related** (mention of screen readers, WCAG, keyboard nav, aria-\*, focus order, contrast, etc.) → add `Area: Accessibility` and assign `smhigley` alongside the area owner. +6. **Duplicate** → `Resolution: Duplicate` + comment linking the original. Before claiming a duplicate, actually search: `gh issue list --repo microsoft/fluentui --search '<keywords>' --limit 10`. +7. **Spam / not-an-issue** → `Resolution: Not An Issue`, close. + +## Area-owner routing + +Route to people first, then to teams. Teams are in [docs/team-routing.md](../../../docs/team-routing.md) and `.github/CODEOWNERS` is the source of truth for package ownership. + +| Area | Assignee | Trigger | +| ---------------------------------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------ | +| Web Components (`@fluentui/web-components`, `Fluent UI WC (v3)`) | `chrisdholt` | Any `web-components` label, or title mentions `fluent-*` custom elements | +| Charting (`@fluentui/charts`) | `AtishayMsft` | `Package: charting`, chart component names | +| Date/Time pickers | `ermercer` | DatePicker, Calendar, TimePicker | +| Accessibility (any product) | `smhigley` | a11y keywords; also assigned in addition to product owner | +| Northstar (legacy v0) | `jurokapsiar` | `Fluent UI react-northstar` | +| v9 React components | CODEOWNERS owner for the specific package | Everything under `packages/react-components/*` | +| v8 React components | CODEOWNERS owner for the specific package | Everything under `packages/react/*` | + +For v9/v8 components, look up the package owner. Fast path: + +```bash +# Example: find the CODEOWNERS entry for react-skeleton +grep -n 'react-skeleton' /Users/doidor/src/microsoft/fluentui/.github/CODEOWNERS || true +``` + +If CODEOWNERS lists a team (e.g., `@microsoft/cxe-prg`), leave `assignee` empty and put the team in `needs_human_followup` — teams can't be assigned on issues via `gh`, so the user will route manually. + +## Priority and partner-ask signals + +Every issue goes through the same decision tree regardless of author — that's the default. `Shield: P1` and `Partner Ask` are narrow exceptions, not shortcuts around triage. + +See `references/partner-orgs.md` for the framing (what these labels mean, what they _don't_ mean, and how to avoid misusing them). + +If the triager has private notes in their Claude memory about tracked partner workstreams and known reporter handles, use that context to tip ambiguous cases. If no such memory exists, treat affiliation as unknown and flag `needs_human_followup: "verify partner status"` rather than guessing. A wrong `Partner Ask` label is a worse outcome than a missing one — the label can be added in five seconds later. + +## Workflow + +### Step 1 — Fetch + +Run the queue query. Show the user the count and a one-line summary per issue (number, title, author, age in days). Don't dump the full JSON. + +### Step 2 — Classify + +For each issue, read the title + body + existing labels. If repro evidence is ambiguous, skim the linked StackBlitz / attached code block — don't mark it `needs-repro` just because the word "Reproduction" field in the template is empty; check the actual content. + +**For feature requests that cite v8 behavior**, do a targeted investigation before deciding. Look in the relevant package for: + +- `packages/react-components/react-<name>/library/docs/Spec.md` — design rationale +- `packages/react-components/react-<name>/library/docs/MIGRATION.md` — explicit v8→v9 migration notes, often documents dropped props and their replacements +- `packages/react-components/react-motion-components-preview/` — motion/transition primitives (if the ask is animation/transition related) +- `packages/react-components/react-field/` — label/description/validation composition (if the ask is form-control adornments) +- The component's stories for worked examples of the composition pattern + +v9 defaults to composition over configuration. An ask like "add a `shouldFadeIn` prop" usually has an answer like "compose with `<Fade>`" or "use `onLoad` + `makeStyles` keyframes". Surface the v9 pattern in the comment so the reporter isn't left hanging. + +When checking for duplicates, search first: + +```bash +gh issue list --repo microsoft/fluentui \ + --search 'is:issue <keywords>' \ + --limit 10 \ + --json number,title,state +``` + +Keep each issue's classification in an in-memory structure (don't write intermediate files). + +### Step 3 — Present recommendations + +Produce a compact table the user can scan. One row per issue. Columns: `#`, `classification`, `add labels`, `remove labels`, `assignee`, `priority`, `notes`. Put the `comment` drafts separately below the table, keyed by issue number — comments are long and clutter the table. + +If you found duplicates or partner asks, call them out explicitly above the table. + +**Then propose a validation set.** Below the table, list the bugs you flagged as `validation_candidate: true` with a one-line reason each, and ask the user to confirm. Take the initiative here — don't wait for the user to think of validation. Example: + +``` +I'd like to verify these repros with playwright-cli before triaging: + • #35998 — StackBlitz provided; Link underline visual bug, testable headless + • #36001 — CodeSandbox provided; Button click handler not firing + +Confirm with `validate yes`, `validate all`, a subset like `validate 35998`, +or `skip validation` to move straight to approval. +``` + +If there are no validation candidates (all feature requests, all reports with root-cause diffs, etc.), say so explicitly and go straight to Step 4. + +**How to decide `validation_candidate`:** + +`true` when all of: + +- The issue is a bug (not a feature or question). +- A reproduction exists — either a sandbox URL (StackBlitz / CodeSandbox / JSFiddle / CodePen) in the body, or a clearly-described behavior against a specific component that a Storybook default story can exercise. +- The expected failure is something headless Chromium can observe: visual layout, DOM structure, console errors, aria attributes, event handlers firing. + +`false` when any of: + +- It's a feature request (nothing to reproduce). +- The author already included a root-cause analysis + a suggested diff (they did the work; headless adds noise, not signal). +- The bug is about performance, memory, or timing (perf A/B metrics, rAF regressions, FCP deltas) — headless won't give reliable numbers. +- The bug is browser-specific (Edge-only, Safari-only, mobile) — you can't run those from here. +- The bug requires assistive tech (screen reader behavior, voice control) — headless can see ARIA but not AT interpretation. +- The bug requires real user timing (typing debounce, drag gestures, animation pauses). + +### Step 3.5 — Validate repros with playwright-cli + +Run this step when the user confirms the validation set you proposed in Step 3 (or when they name a different set — `validate 35998 35976`, `validate all`, etc.). If the user says `skip validation`, go straight to Step 4. + +Validation is the human-in-the-loop gate on agent initiative: you proposed the set, the human confirmed it, now execute. Do not mutate any issue as a result of validation — evidence feeds back into the recommendation table, and the user still approves in Step 4. + +For each confirmed issue: + +1. **Prerequisites.** Make sure playwright-cli is available (same install as `visual-test`): + + ```bash + npm ls -g @playwright/cli 2>/dev/null || npm install -g @playwright/cli@0.1.1 + ``` + +2. **Pick the target URL.** In priority order: + + - A StackBlitz / CodeSandbox / JSFiddle / CodePen link in the issue body — use it directly. + - A link to a specific Storybook story hosted at `storybooks.fluentui.dev` or similar — use it directly. + - Otherwise: spin up the relevant component's **per-component** Storybook (e.g. `yarn nx run react-<component>-stories:storybook --skip-nx-cache`). Follow the full pattern in the `visual-test` skill for port detection and troubleshooting. + + **Never use the workspace-wide Storybook for validation** — `yarn storybook` at the repo root or `public-docsite-v9` pulls in deprecated unstable re-exports and hits HMR restart loops that make validation useless. If the per-component Storybook fails to boot, fall back to the `visual-test` skill's troubleshooting section rather than reaching for the workspace-wide one. + +3. **Capture evidence.** Don't try to "prove" the bug — just gather what a human needs to decide: + + ```bash + playwright-cli goto "<repro URL>" + playwright-cli screenshot --filename=/tmp/triage-<num>-screenshot.png + playwright-cli snapshot > /tmp/triage-<num>-dom.txt + # Console errors and warnings — often the most useful signal + playwright-cli console > /tmp/triage-<num>-console.txt + ``` + + If the sandbox has a specific interaction to trigger the bug (click a button, type in a field), follow it using refs from the snapshot. + +4. **Classify the result** as one of: + + - `repros` — you observed the described behavior. Include the screenshot path and any console errors. + - `does_not_repro` — you visited the sandbox, ran through the described steps, and the behavior didn't appear. Include what you tried so the human can sanity-check. + - `cannot_determine` — the sandbox didn't load, requires auth, times out, needs a specific browser version (common for perf / Edge-only issues), or needs real user timing that headless can't reproduce. **This is the most important category** — don't pretend headless gives you environment coverage it doesn't. + +5. **Feed evidence back into the recommendation.** Only update `add_labels` as follows: + + - `does_not_repro` → surface `Resolution: Can't Repro` as a **candidate**, not a decision. The user still approves. Attach the evidence in the comment draft so they can judge. + - `repros` → leave labels unchanged; the existing classification stands. Maybe upgrade priority if the evidence makes it clearly worse than the body described. + - `cannot_determine` → leave labels unchanged; note the limitation in `needs_human_followup`. + +6. **Clean up.** Close the browser, kill any Storybook you started, and delete the evidence files unless the user wants to keep them: + + ```bash + playwright-cli close + # If you started Storybook: + lsof -i :$SB_PORT -t 2>/dev/null | xargs kill 2>/dev/null + ``` + +The validation-candidate heuristics are in Step 3 — don't re-litigate them here. If the user overrides and asks you to validate something you flagged as not-a-candidate (e.g., the Edge perf issue), do it anyway but prefix the result with a warning about headless limitations so the evidence is interpreted correctly. + +### Step 4 — Ask for approval + +Ask the user: "Apply all / approve specific numbers / skip / edit?". Accept inputs like `apply 35976, 35977`, `skip 36007`, or free-form edits. The user can still request additional validation at this point (`validate 36007` even if you hadn't proposed it); if they do, loop back through Step 3.5 and then re-present the updated table. Don't proceed to apply until the user has explicitly approved. + +### Step 5 — Apply approved changes + +Use `gh issue edit` for labels and assignees. Use `gh issue comment` for drafted comments. Apply one issue at a time and print a one-line result per issue (success or error). Do not retry blindly on failure; stop and ask the user what to do. + +```bash +# Labels +gh issue edit <num> --repo microsoft/fluentui \ + --add-label "Type: Bug :bug:" \ + --add-label "Fluent UI react-components (v9)" \ + --remove-label "Needs: Triage :mag:" + +# Assignee +gh issue edit <num> --repo microsoft/fluentui --add-assignee chrisdholt + +# Comment (use heredoc) +gh issue comment <num> --repo microsoft/fluentui --body "$(cat <<'EOF' +Thanks for the report! Could you share a minimal StackBlitz or CodeSandbox that reproduces this? The issue template has a Reproduction field that's currently empty. +EOF +)" +``` + +### Step 6 — Summarize + +After applying, print: X issues triaged, Y still need human follow-up (list them), Z were skipped. Leave the user with a clear handoff. + +## Anti-patterns (don't do these) + +- **Don't apply changes silently.** Always present the recommendation table first. +- **Don't guess at labels that don't exist.** The allow-list in `references/triage-labels.md` is exhaustive for triage — if a situation calls for a label that isn't there, ask the user. +- **Don't mark issues as duplicates without linking the original.** A `Resolution: Duplicate` label without a comment pointing to the original is worse than no triage. +- **Don't remove `Needs: Triage :mag:` on issues you're flagging for human follow-up.** If you can't decide, leave the triage label on so it stays in the queue. +- **Don't assign a team as if it were a user.** `gh issue edit --add-assignee` only accepts user logins. For team routing, leave the assignee empty and note the team in `needs_human_followup`. +- **Don't say "we'll fix this soon" in comments.** The guidelines are explicit: only promise work we're committed to delivering. + +## Reference files + +- `references/shield-guidelines.md` — full triage decision tree from the Shield dev guidelines +- `references/triage-labels.md` — exact label names allowed in triage +- `references/partner-orgs.md` — what `Shield: P1` / `Partner Ask` mean and, importantly, what they don't diff --git a/.agents/skills/triage-issues/evals/evals.json b/.agents/skills/triage-issues/evals/evals.json new file mode 100644 index 0000000000000..e3335b7d4ec5f --- /dev/null +++ b/.agents/skills/triage-issues/evals/evals.json @@ -0,0 +1,23 @@ +{ + "skill_name": "triage-issues", + "evals": [ + { + "id": 1, + "prompt": "triage new issues", + "expected_output": "Fetches the Needs: Triage :mag: queue, classifies each issue, presents a recommendation table, and waits for approval before applying any gh mutations.", + "files": [] + }, + { + "id": 2, + "prompt": "can you go through the fluent ui triage queue and tell me what needs labels?", + "expected_output": "Same flow as above — fetches the queue and produces per-issue recommendations. Should correctly route web-components issues to the web-components owner and surface reports that document a critical regression as `Shield: P1` candidates.", + "files": [] + }, + { + "id": 3, + "prompt": "run shield triage on the open issues", + "expected_output": "Fetches queue, classifies, produces recommendations. Should not apply any labels without approval. Should correctly identify feature requests and recommend 'Needs: Backlog review'.", + "files": [] + } + ] +} diff --git a/.agents/skills/triage-issues/references/partner-orgs.md b/.agents/skills/triage-issues/references/partner-orgs.md new file mode 100644 index 0000000000000..54695729c10d6 --- /dev/null +++ b/.agents/skills/triage-issues/references/partner-orgs.md @@ -0,0 +1,44 @@ +# High-priority reporter signals + +Every issue in the triage queue is treated as first-class, regardless of who filed it. External community reports and internal reports go through the same decision tree (classify, check repro, route to owner), and the same SLAs apply to all open v9/v8 bugs. + +This file exists because the Shield workflow has one narrow exception: when a report documents a clearly critical regression — with root-cause analysis, measured impact data, or a large blast radius — it can be flagged as `Shield: P1` to pull it to the front of the queue. That flag is about the **nature of the report**, not the reporter. + +## When to consider `Shield: P1` + +- The issue documents a measured regression (numbers, metrics, profiling data). +- The issue points to a specific root cause in the code, often with a suggested diff. +- The issue affects a broad surface (many components, many consumers) rather than a single edge case. +- The workaround is genuinely unavailable (not just "we'd prefer not to"). + +The presence of any one of these is a hint, not a trigger. All of them together is a strong signal. None of them — even for a report from an internal team — means normal-priority flow. + +## When to consider `Partner Ask` + +The `Partner Ask` label exists for a narrow operational reason: some downstream Microsoft teams rely on Fluent UI and their feedback loop is tracked separately for roadmap/reporting purposes. The label captures that a report originated in one of those workstreams so the PM team can roll up partner-facing impact. It is not a quality or priority judgment on the issue itself — community issues frequently hit the queue ahead of partner-tagged issues because their triage outcome is clearer. + +If you can't tell from the issue body whether a report qualifies, **don't guess and don't apply the label**. Leave it off and flag `needs_human_followup: "verify whether this is a partner workstream"`. A missing `Partner Ask` label can be added in 5 seconds later; a wrong one is noisier and communicates the wrong thing. + +## What this skill does not do + +- **Does not downrank community issues.** External contributors get the same triage, labels, assignee routing, and comment quality as anyone else. +- **Does not treat author identity as a priority signal on its own.** GitHub login is used only to look up context (previous issues, stated affiliation), never as an override for the decision tree above. +- **Does not publish a partner list.** The operational specifics of which teams use the `Partner Ask` label live outside this repo (in the triager's private notes / internal Shield docs). If you need them for a triage session, the maintainer will surface them; they are not required for a correct triage decision on any individual issue. + +## Verification commands + +Look up an author's public profile (name, company, bio) when the issue body doesn't make context clear: + +```bash +gh api users/<login> -q '{login, name, company, bio}' +``` + +Check prior triage decisions on the same author's issues to see how the team has historically handled similar reports: + +```bash +gh issue list --repo microsoft/fluentui \ + --search 'is:issue author:<login>' \ + --limit 5 --json number,title,labels,state +``` + +Neither of these determines priority — they just give you context the decision tree can use. diff --git a/.agents/skills/triage-issues/references/shield-guidelines.md b/.agents/skills/triage-issues/references/shield-guidelines.md new file mode 100644 index 0000000000000..300d933ffa46b --- /dev/null +++ b/.agents/skills/triage-issues/references/shield-guidelines.md @@ -0,0 +1,75 @@ +# Shield Triage Guidelines (distilled) + +Source: the internal Shield Dev Guidelines doc (Gouttierre Gomes, 15 Jan 2024). This file captures the triage decision tree in text — refer back to the original doc for the surrounding context (R.O.B., PR review, communication channels). + +## The core question + +Shield human triage processes issues labeled `Needs: Triage :mag:` (after the Shield BOT has already auto-routed the obvious ones). For each issue, you're deciding: + +1. **Is this even an issue?** (spam, not-an-issue, question-should-be-discussion) +2. **Is there a reliable repro?** (if not, block on author) +3. **What product and version?** (v9 / v8 / v7 / web-components / charting / northstar) +4. **Does the report document a critical regression?** (measured impact, root cause, broad blast radius → P1 candidate) +5. **Who owns it?** (area owner or package owner) + +## Decision tree + +``` +issue labeled "Needs: Triage :mag:" +├── Is it a question / support request? +│ └── yes → convert to Discussion, close issue +├── Is it an accessibility issue? +│ └── yes → add "Area: Accessibility", assign smhigley (in addition to owner), add to v-a11y board +├── Does it have a reliable repro? +│ └── no → add "Needs: Repro" + "Needs: Author Feedback", comment asking for repro +├── What version? +│ ├── v9 (N) → always resolve; highest priority among open bugs +│ ├── v8 (N-1) → resolve normally +│ └── v7 → generally "Resolution: Won't Fix"; maintainer may make exceptions for exceptional-impact reports +├── Does the report have tracked-workstream context (per triager's private notes)? +│ └── yes → add "Partner Ask" if the body confirms the workstream reference. If also a clear critical bug, add "Shield: P1" + notify Shield Teams channel +├── Is it a feature request? +│ └── yes → add "Needs: Backlog review", add to unified Backlog +├── Is it a clear critical bug (even without partner)? +│ └── yes → add "Shield: P1", notify Shield Teams channel +├── Is it reasonable for a community contributor? +│ └── yes → add "Help Wanted" +├── Can a newcomer tackle it? +│ └── yes → add "Good First Issue" +└── Otherwise → assign area owner, remove "Needs: Triage :mag:" +``` + +## Area routing (fixed assignments per the guidelines) + +| Area | Assignee GitHub login | +| --------------------------- | --------------------- | +| Northstar (v0) | `jurokapsiar` | +| Web Components (v3) | `chrisdholt` | +| Charting | `AtishayMsft` | +| Date/Time pickers | `ermercer` | +| Accessibility (any product) | `smhigley` | + +For v8 and v9 React components, use `.github/CODEOWNERS` to find the package owner. CODEOWNERS often lists a team (`@microsoft/cxe-prg`, `@microsoft/cxe-red`); teams can't be assigned to issues, so leave the assignee blank and surface the team for the user to route. + +## Version-support matrix + +| Version | Status | Priority treatment | +| --------------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------- | +| v9 (`Fluent UI react-components (v9)`) | Active (N) | Highest — always resolve | +| v8 (`Fluent UI react (v8)`) | Maintenance (N-1) | Resolve normally | +| Web Components v3 (`Fluent UI WC (v3)`) | Active | Resolve normally | +| Charting | Active | Resolve normally | +| v7 (`Fluent UI react (v7)`) | EOL | Generally close as "Won't Fix"; a maintainer may make exceptions for reports with exceptional business-impact signals | +| Northstar (`Fluent UI react-northstar`) | EOL | Route to `jurokapsiar`, generally won't-fix | + +## "We'll fix this soon" is off-limits + +The guidelines explicitly call this out. When commenting on an issue: + +- Acknowledge, point to existing docs / workarounds, offer to review a PR. +- Do **not** commit the team to a fix unless a dev has already picked it up. +- Avoid dates. "We'll get to this in Q2" creates an obligation. + +## When to stop and hand off + +The guidelines are clear: if you can't get an issue to a reasonable resolution state, do a **positive hand-off** — name the next owner explicitly and assign them. Don't just leave it floating with a partial triage. In skill terms: flag `needs_human_followup` with a specific ask ("check whether the reported repro still happens on latest", "check if #33888 should be reopened"). diff --git a/.agents/skills/triage-issues/references/triage-labels.md b/.agents/skills/triage-issues/references/triage-labels.md new file mode 100644 index 0000000000000..82d99360f4633 --- /dev/null +++ b/.agents/skills/triage-issues/references/triage-labels.md @@ -0,0 +1,89 @@ +# Triage label allow-list + +These are the labels actually present on the `microsoft/fluentui` repo as of this skill's writing. Don't recommend anything that isn't here — `gh` will fail, and invented labels slip past the user during approval. + +To refresh this list against the live repo: + +```bash +gh api repos/microsoft/fluentui/labels --paginate -q '.[] | .name' | sort +``` + +## Product labels (pick exactly one) + +- `Fluent UI react-components (v9)` +- `Fluent UI react (v8)` +- `Fluent UI react (v7)` +- `Fluent UI WC (v3)` (use together with `web-components`) +- `web-components` +- `Fluent UI react-northstar` / `Fluent UI react-northstar (v0)` +- `Fluent UI vNext` +- `Package: charting` + +## Type labels (pick exactly one) + +- `Type: Bug :bug:` +- `Type: Feature` + +## Status labels + +- `Needs: Triage :mag:` — remove once triage is done (unless flagged for human follow-up) +- `Needs: Author Feedback` — pair with repro/info requests +- `Needs: Repro` — when the reproduction is missing or unusable +- `Needs: Actionable Feedback :female_detective:` — author replied but still not actionable +- `Needs: Attention` — needs team attention, not blocking on author +- `Needs: Backlog review` — for feature requests going to the unified Backlog +- `Needs: Investigation` — dev needs to investigate before further action +- `Needs: Discussion` — requires team discussion +- `Needs: Design` — needs design input +- `Needs: PM` — needs PM input +- `Needs: API Breaking Change` / `Needs: Behavior Breaking Change` — flag breaking-change impact + +## Priority / ownership labels + +- `Shield: P1` — critical / partner-driven; notify Shield Teams channel after applying +- `Shield: P2` — elevated but not P1 +- `Partner Ask` — report originates in a tracked partner workstream (see `partner-orgs.md` for what this does and does not mean) +- `Help Wanted ✨` — community can take this +- `Good First Issue 🏆` — scoped well for a newcomer +- `From Shield` — marks it as triaged via the Shield workflow + +## Area labels (add as applicable — not exhaustive) + +- `Area: Accessibility` — always pair with `smhigley` assignment +- `Area: Performance` +- `Area: Testing` +- `Area: Theming` +- `Area: Icons` +- `Area: SSR` +- `Area: Documentation` +- `Area: Positioning` + +(Full list has ~40 Area labels — run the refresh command above if an area doesn't seem covered.) + +## Component labels + +Format: `Component: <Name>` (e.g., `Component: Announced`). Add when the issue targets a specific component. + +## Resolution labels (only when closing) + +- `Resolution: Duplicate` — must be accompanied by a comment linking the original +- `Resolution: Not An Issue` — spam or invalid +- `Resolution: By Design` — working as intended; explain why in the comment +- `Resolution: Can't Repro` — after investigation, can't reproduce +- `Resolution: External` — caused by a dependency or external factor +- `Resolution: Won't Fix` / `Resolution: Won't Do` — out of support or scope +- `Resolution: Soft Close` — closed for inactivity but can be reopened + +## Label-combination heuristics + +| Situation | Labels to add | +| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | +| v9 bug, good repro, not a partner | `Type: Bug :bug:`, `Fluent UI react-components (v9)`, possibly `Component: <X>` | +| v9 feature request (no v9-native pattern exists) | `Type: Feature`, `Fluent UI react-components (v9)`, `Needs: Backlog review` | +| v9 feature request porting v8 behavior that v9 addresses via composition | `Type: Feature`, `Fluent UI react-components (v9)`, `Resolution: By Design` — and close with a comment pointing to the pattern | +| Web-components bug | `Type: Bug :bug:`, `web-components`, `Fluent UI WC (v3)` | +| Bug with no repro | `Type: Bug :bug:`, `<product>`, `Needs: Repro`, `Needs: Author Feedback` | +| A11y issue | (above) + `Area: Accessibility` | +| Partner ask, critical | (above) + `Partner Ask` + `Shield: P1` | +| Good first issue candidate | (above) + `Good First Issue 🏆` + `Help Wanted ✨` | +| Duplicate | `Resolution: Duplicate` + comment with link to original | diff --git a/.agents/skills/visual-test/SKILL.md b/.agents/skills/visual-test/SKILL.md index 1946dea2ca841..18a32e5279297 100644 --- a/.agents/skills/visual-test/SKILL.md +++ b/.agents/skills/visual-test/SKILL.md @@ -18,40 +18,74 @@ Ensure `playwright-cli` is installed globally: npm ls -g @playwright/cli 2>/dev/null || npm install -g @playwright/cli@0.1.1 ``` +## Critical: use the per-component Storybook only + +**Never** use `yarn storybook` at the repo root, `public-docsite-v9`, or any workspace-wide Storybook build for visual validation. They pull in every v9 package (including deprecated `unstable` re-exports) and will either fail to compile due to missing packages or get stuck in HMR restart loops — the validation becomes useless. Always boot the **per-component stories package**, which only imports its own component's stories and dependencies. + ## Steps -1. **Find the component's stories package.** Each v9 component has a dedicated stories package: +1. **Find the component's stories package.** Each v9 component has a dedicated stories package named `react-<component>-stories`: + + ```bash + yarn nx show projects 2>/dev/null | grep "^react-<lowercase-component-name>-stories$" + ``` + + If the grep returns nothing, the component doesn't have its own stories package — check for a preview package (`react-<component>-preview-stories`) or ask before proceeding. + +2. **Start the component's Storybook dev server.** Use the `storybook` target on the stories project directly — it's the most portable, since library aliases like `react-<component>:start` were only added in April 2026 and may not exist in older workspace snapshots: ```bash - yarn nx show projects 2>/dev/null | grep "<lowercase-component-name>.*stories" + yarn nx run react-<component>-stories:storybook --skip-nx-cache & + NX_PID=$! ``` -2. **Start the component's Storybook dev server:** + `--skip-nx-cache` matters: the `storybook` target is (unusually) declared with `cache: true`, and a prior run can replay cached output and exit without actually starting a server. + +3. **Find the storybook port.** Three quirks to know: + + - Storybook picks a **random high port** on first boot (e.g. `49360`), not the Storybook default `6006`. Don't assume. + - The nx wrapper process often exits 0 after delegating to storybook, leaving the actual server running as a child. So the nx PID isn't the storybook PID. + - The storybook child opens **two** listening sockets: one for HTTP content, one for the webpack HMR event-stream. They are not ordered — either one can be numerically lower. Picking by port number is unreliable; pick by `Content-Type`. + + Reliable detection — target the storybook node child (not the yarn wrapper), then probe each listening socket until one returns `text/html`: ```bash - yarn nx run react-<component>:start & + # Wait up to 180s for the storybook child to bind an HTTP port. + # Pattern matches the node child specifically, not `yarn storybook dev` (the wrapper has no sockets). + for i in $(seq 1 180); do + SB_CHILD=$(pgrep -f "node.*\.bin/storybook dev" | head -1) + if [ -n "$SB_CHILD" ]; then + for port in $(lsof -a -p "$SB_CHILD" -i -P -sTCP:LISTEN 2>/dev/null | awk 'NR>1 {print $9}' | sed 's/.*://'); do + CT=$(curl -sI --max-time 2 "http://localhost:$port/" 2>/dev/null | grep -i '^content-type:' | grep -i 'text/html') + if [ -n "$CT" ]; then SB_PORT=$port; break; fi + done + if [ -n "$SB_PORT" ]; then break; fi + fi + sleep 1 + done + echo "Storybook child PID=$SB_CHILD on port $SB_PORT" ``` - The port is **dynamic** — parse it from the Storybook startup output. Look for the `Local:` line - (e.g. `Local: http://localhost:61582/`). Extract the port and store it in a variable: + Then wait for Storybook to finish compiling stories — the HTTP port answers before `index.json` is populated: ```bash - # Wait for Storybook to print its URL, then extract the port - # Or poll common ports until one responds: - for port in 6006 61582 $(seq 6007 6020); do - STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:$port 2>/dev/null) - if [ "$STATUS" = "200" ]; then SB_PORT=$port; break; fi + for i in $(seq 1 60); do + N=$(curl -s --max-time 2 "http://localhost:$SB_PORT/index.json" 2>/dev/null \ + | python3 -c "import json,sys; print(len(json.load(sys.stdin).get('entries', {})))" 2>/dev/null || echo 0) + if [ "$N" -gt 0 ]; then break; fi + sleep 2 done - echo "Storybook on port $SB_PORT" ``` -3. **Open the page with playwright-cli:** + If no port turns up, or `index.json` never populates — **do not** fall back to the workspace-wide Storybook; read the nx output log and debug the per-component boot. The most common real failure is missing build artifacts for unstable re-export deps (see troubleshooting below). + +4. **Open the page with playwright-cli:** ```bash playwright-cli open "http://localhost:$SB_PORT" ``` -4. **Navigate to the specific story iframe** and capture a screenshot. +5. **Navigate to the specific story iframe** and capture a screenshot. Use the iframe URL for a clean render without Storybook chrome: ```bash @@ -59,9 +93,9 @@ npm ls -g @playwright/cli 2>/dev/null || npm install -g @playwright/cli@0.1.1 playwright-cli screenshot --filename=/tmp/visual-test-$ARGUMENTS.png ``` -5. **View the screenshot** using the Read tool to visually inspect the rendered component. +6. **View the screenshot** using the Read tool to visually inspect the rendered component. -6. **Use `snapshot`** to get the accessibility tree and find interactive element refs: +7. **Use `snapshot`** to get the accessibility tree and find interactive element refs: ```bash playwright-cli snapshot @@ -69,15 +103,41 @@ npm ls -g @playwright/cli 2>/dev/null || npm install -g @playwright/cli@0.1.1 Then interact with elements by ref (e.g., click, hover) before taking more screenshots. -7. **If the component doesn't look right**, go back to the code, fix the issue, and repeat from step 4 (Storybook hot-reloads changes). +8. **If the component doesn't look right**, go back to the code, fix the issue, and repeat from step 4 (Storybook hot-reloads changes). -8. **Clean up** when done: +9. **Clean up** when done: ```bash playwright-cli close - # Kill storybook by port + # Kill storybook — the nx wrapper may already be gone, so target the child + [ -n "$SB_CHILD" ] && kill "$SB_CHILD" 2>/dev/null lsof -i :$SB_PORT -t 2>/dev/null | xargs kill 2>/dev/null ``` +## Troubleshooting + +**`yarn nx run react-<component>-stories:storybook` says the target doesn't exist.** +The workspace graph may be stale (recent reparent). Run `yarn nx reset` then retry. If `start` aliases still don't exist, use the direct yarn invocation: + +```bash +cd packages/react-components/react-<component>/stories && yarn storybook dev --port 0 & +# --port 0 asks Storybook to pick a free port; detect it via the pgrep/lsof pattern above +``` + +**The build stops mid-compilation and nx "completes" with exit 0.** +That's the nx cache replaying a prior partial run. Always pass `--skip-nx-cache`. + +**The build fails with `Module not found: @fluentui/react-alert`, `@fluentui/react-infobutton`, or `@fluentui/react-virtualizer`.** +These three packages are listed as deps of `@fluentui/react-components` and are linked into `node_modules` from local workspace sources (`packages/react-components/deprecated/react-alert/`, `deprecated/react-infobutton/`, `react-virtualizer/`). Their `lib-commonjs/` output only exists after they're built. On a fresh clone this build hasn't run yet, so the linked entry points at files that don't exist. Fix: + +```bash +yarn nx run-many -t build -p react-alert,react-infobutton,react-virtualizer +``` + +Then restart the storybook target. This is a real infrastructure wart — it affects **any** per-component Storybook that pulls stories from the shared `@fluentui/react-components` barrel, not just combobox. (If you see the error elsewhere in this repo — e.g. the workspace-wide Storybook — the same fix applies plus the "don't use workspace-wide" rule above still stands.) + +**The story loads but keeps reloading (`[HMR] Cannot find update (Full reload needed)`).** +Same root cause — you're on the workspace-wide Storybook, which has HMR issues when preview packages rebuild. Kill it and use the per-component one. + ## Story ID Pattern Story IDs follow the pattern `<category>-<component>--<story>`: diff --git a/.claude/skills/triage-board/SKILL.md b/.claude/skills/triage-board/SKILL.md new file mode 100644 index 0000000000000..e954a19497cd1 --- /dev/null +++ b/.claude/skills/triage-board/SKILL.md @@ -0,0 +1 @@ +@../../../.agents/skills/triage-board/SKILL.md diff --git a/.claude/skills/triage-issues/SKILL.md b/.claude/skills/triage-issues/SKILL.md new file mode 100644 index 0000000000000..b78daee3046fb --- /dev/null +++ b/.claude/skills/triage-issues/SKILL.md @@ -0,0 +1 @@ +@../../../.agents/skills/triage-issues/SKILL.md diff --git a/AGENTS.md b/AGENTS.md index 4e22b956eed4d..9b5917aaa4380 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -91,15 +91,17 @@ state.root.className = mergeClasses( ## Skills (Slash Commands) -| Skill | Command | Purpose | -| -------------- | -------------------- | ---------------------------------------------------------- | -| `v9-component` | `/v9-component Name` | Scaffold a new v9 component with all required files | -| `change` | `/change` | Create beachball change file from current diff | -| `lint-check` | `/lint-check [pkg]` | Run lint, parse errors, and auto-fix common issues | -| `token-lookup` | `/token-lookup val` | Find the design token for a hardcoded CSS value | -| `package-info` | `/package-info pkg` | Quick lookup: path, deps, owner, tests, structure | -| `visual-test` | `/visual-test Name` | Visually verify a component via Storybook + playwright-cli | -| `review-pr` | `/review-pr #123` | Review a PR with confidence scoring and category checks | +| Skill | Command | Purpose | +| --------------- | -------------------- | ---------------------------------------------------------- | +| `v9-component` | `/v9-component Name` | Scaffold a new v9 component with all required files | +| `change` | `/change` | Create beachball change file from current diff | +| `lint-check` | `/lint-check [pkg]` | Run lint, parse errors, and auto-fix common issues | +| `token-lookup` | `/token-lookup val` | Find the design token for a hardcoded CSS value | +| `package-info` | `/package-info pkg` | Quick lookup: path, deps, owner, tests, structure | +| `visual-test` | `/visual-test Name` | Visually verify a component via Storybook + playwright-cli | +| `review-pr` | `/review-pr #123` | Review a PR with confidence scoring and category checks | +| `triage-issues` | `/triage-issues` | Walk the Needs-Triage queue and recommend labels/assignee | +| `triage-board` | `/triage-board` | Route untriaged items on the Fluent Unified project board | ## Package Layout diff --git a/docs/workflows/contributing.md b/docs/workflows/contributing.md index 34494467790d5..5cb9606bbd485 100644 --- a/docs/workflows/contributing.md +++ b/docs/workflows/contributing.md @@ -25,6 +25,16 @@ yarn nx affected -t test # Test affected projects yarn create-component # Interactive generator ``` +### First-time Storybook setup + +After `yarn install`, per-component Storybooks that pull stories through the shared `@fluentui/react-components` barrel (most v9 components) will fail to compile with `Module not found: @fluentui/react-alert`, `@fluentui/react-infobutton`, or `@fluentui/react-virtualizer`. These three packages are workspace-linked from local sources — their built output isn't present on a fresh clone. Build them once before starting any component Storybook: + +```bash +yarn nx run-many -t build -p react-alert,react-infobutton,react-virtualizer +``` + +The build only needs to be re-run if you modify one of those three packages. Normal rebuilds of any other component pick up these prebuilt deps automatically. + ## PR Checklist 1. **Change file** — Required for any published package change: