Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
d1e7bb9
Planning docs
vedrani Apr 28, 2026
a34a7df
Planning docs
vedrani Apr 30, 2026
977f54c
Planning docs
vedrani May 4, 2026
1502273
Orchestrator
vedrani May 5, 2026
320b28e
Orchestrator fix
vedrani May 5, 2026
48aa687
Orchestrator fix #2
vedrani May 5, 2026
7afd1d6
Orchestrator fix #3
vedrani May 6, 2026
3d77330
Orchestrator fix #4
vedrani May 6, 2026
7e6a516
Orchestrator fix #5
vedrani May 6, 2026
eb53d77
Orchestrator fix #6
vedrani May 6, 2026
7849c79
Orchestrator fix #8
vedrani May 6, 2026
84bad84
Orchestrator fix #9
vedrani May 6, 2026
efeb339
Orchestrator fix #10
vedrani May 6, 2026
2367a53
Orchestrator tier1.phase3
vedrani May 6, 2026
6b6734a
Orchestrator fix #1
vedrani May 6, 2026
57b4ed9
Orchestrator phase3.5
vedrani May 6, 2026
54b888c
Orchestrator fix #1
vedrani May 6, 2026
1c4c0d8
Orchestrator fix #2
vedrani May 6, 2026
5d50b2e
Orchestrator fix #2
vedrani May 6, 2026
8d59656
Orchestrator Tier.2
vedrani May 6, 2026
61cabb9
Orchestrator v4
vedrani May 7, 2026
66dbc6a
Orchestrator fix #1
vedrani May 7, 2026
9a5b651
Orchestrator fix #2
vedrani May 7, 2026
c473f47
Orchestrator fix #3
vedrani May 7, 2026
0922534
Orchestrator fix #4
vedrani May 7, 2026
397998e
Orchestrator fix #5
vedrani May 7, 2026
e711eac
Orchestrator fix #5
vedrani May 7, 2026
a8a5af9
Orchestrator fix #6
vedrani May 7, 2026
78a5a3e
Orchestrator fix #7
vedrani May 7, 2026
5365e01
Orchestrator fix #8
vedrani May 7, 2026
3ce45bf
Orchestrator fix #9
vedrani May 7, 2026
233e5ee
Orchestrator fix #10
vedrani May 8, 2026
e9c9bc3
Orchestrator security + classes
vedrani May 11, 2026
4a21fc7
Orchestrator review-sweep fix#1
vedrani May 11, 2026
6efc6f9
Migrate orchestrator to pnpm
vedrani May 11, 2026
7fd27a0
Migrate orchestrator to pnpm
vedrani May 11, 2026
838529c
Add playwright mcp
vedrani May 12, 2026
0835819
Add playwright mcp
vedrani May 12, 2026
ea7e532
Details
vedrani May 12, 2026
4ed660d
Fix storybook
vedrani May 12, 2026
8a68876
Fix review-sweep
vedrani May 12, 2026
ac09b97
Fix regex
vedrani May 12, 2026
6d44299
Add working-base
vedrani May 12, 2026
ca3c5ba
Fix danger
vedrani May 12, 2026
b48f6e0
Add changeset prompt
vedrani May 12, 2026
f3e7afc
Move pnpm settings
vedrani May 13, 2026
3a4f688
Fix pnpm.lock
vedrani May 13, 2026
f3bc4f6
Fix prompts
vedrani May 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ build
node_modules
!.changeset
.changeset/README.md
docs/migration
docs/modernization
bin
migration-runs
9 changes: 9 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ const generateSameSettingRules = (ruleNames, setting) => {
}

module.exports = {
// root: true stops ESLint from walking up the directory tree looking for
// ancestor configs. Required for `git worktree`-based dev flows
// (orchestrator's worktrees live under `migration-runs/<date>/<comp>/worktree/`,
// and without `root: true`, ESLint walks up from the worktree's source
// through `migration-runs/`, eventually re-discovering this same config in
// the main repo via a different filesystem path. That double-load registers
// `eslint-plugin-ssr-friendly` from two node_modules paths and fails with
// "ESLint couldn't determine the plugin uniquely". Fixed 2026-05-07.)
root: true,
extends: [
require.resolve('@toptal/davinci-syntax/src/configs/.eslintrc.cjs'),
'plugin:ssr-friendly/recommended',
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/danger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Danger
on:
pull_request:
types: [opened, synchronize, reopened, edited, assigned, unassigned]
branches: [master]
branches: [master, 'feature/**']

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand Down
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,17 @@ reports
gha-creds-*.json

llm-docs

# Per-run artifacts emitted by bin/migration-orchestrator.ts (PF-1992).
# Worktrees, gate logs, diff reports, agent prompts/responses, escalation
# blocks. Reproducible per run; do not commit.
migration-runs

# Playwright MCP runtime artifacts emitted into the worktree when the
# orchestrator runs with `--with-mcp` (page snapshots, console logs, etc.).
# Captured by the agent for inspection during iter but should not land in
# the migration PR. Without this entry the agent's `git add -A` step
# (orchestrator-driven) sweeps them into the commit. See PR #4949 for the
# debris example.
.playwright-mcp

6 changes: 0 additions & 6 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

node-linker=hoisted
strict-peer-dependencies=false
auto-install-peers=true
link-workspace-packages=true
resolve-peers-from-workspace-root=true
22 changes: 22 additions & 0 deletions .storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,28 @@ module.exports = {

const { reactDocgen, reactDocgenTypescriptOptions } = typescriptOptions

// Storybook's default rule for .(mjs|tsx?|jsx?) uses babel-loader with
// `exclude: /node_modules/`. That's correct for normal npm packages but
// wrong for `@toptal/picasso-*` workspace packages, which ship their
// `src/` directory alongside `dist-package/` in the published tarball.
// Picasso has at least one cross-package story import (Form's story
// pulls in FormLabel's story via `@toptal/picasso-form-label/src/...`)
// that resolves through node_modules into raw TypeScript source. Without
// babel applied, webpack tries to parse `import type` and fails with
// ModuleParseError. Narrow the exclude so workspace package sources go
// through babel even when resolved through node_modules.
const babelRule = config.module.rules.find(
rule => rule.test && rule.test.toString() === '/\\.(mjs|tsx?|jsx?)$/'
)

if (babelRule) {
babelRule.exclude = filePath =>
filePath.includes('/node_modules/') &&
!/[/\\]node_modules[/\\]@toptal[/\\]picasso-[^/\\]+[/\\]src[/\\]/.test(
filePath
)
}

const cssRule = config.module.rules.find(
rule => rule.test && rule.test.toString().includes('.css')
)
Expand Down
79 changes: 79 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Picasso — Claude Code working notes

Active program: **PI-4318 (Picasso Modernization + AI Developer Experience)**.
Planning docs: `docs/modernization/`

- Effort estimates: `PI-4318-estimates_final.md`
- Calendar: `PI-4318-timeline_final.md`
- Tickets by track: `PI-4318-tickets-by-track_final.md`
- Migration plan deep dive: `PI-4318-P1-MOD-01-migration-plan.md`
- AI leverage spec (orchestrator design): `PI-4318-ai-leverage-tickets.md`

Active migration tooling: `docs/migration/`
- Orchestrator runbook: `docs/migration/ORCHESTRATOR.md`
- Operational migration plan: `docs/migration/migration-plan.md`
- Run: `pnpm orchestrate --component=<Name>` (or `--tier=N`, `--dry-run`, `--no-merge`)

## Code style for orchestrator (`bin/lib/*.ts` and `bin/*.ts`)

Picasso's ESLint config (root `.eslintrc.js`) extends `@toptal/davinci-syntax` and adds `ssr-friendly` + `eslint-plugin-local-rules`. CI's "Static checks" job lints the WHOLE repo (`pnpm eslint --ext=.ts,.tsx,.js,.jsx .`), so any error in `bin/lib/*.ts` blocks the migration PR's CI. Note: `.eslintignore` excludes `bin/` locally, so reproduce CI's behavior with `--no-ignore` when sanity-checking orchestrator changes.

Rules I have personally hit while editing the orchestrator:

- **`no-empty`**: empty blocks fail. `try { ... } catch {}` is wrong — use `catch { /* ignore */ }` or `catch (_err) {}`.
- **`no-control-regex`**: regex literals containing control chars (e.g. `` for ANSI escape) need an `// eslint-disable-next-line no-control-regex` comment.
- **`max-statements-per-line`**: only one statement per line. Avoid `try { x() } catch {}` on one line.
- **`padding-line-between-statements`** + **`jest-formatting/padding-around-all`**: blank lines required around return, const-after-statement, expect blocks, etc.
- **`max-statements`** / **`complexity`**: arrow-function/method complexity capped (~20/10 default).
- **`func-style`**: prefer expressions over declarations in some contexts.

Before declaring orchestrator changes done, ALWAYS run:

```bash
pnpm eslint --ext=.ts --no-ignore bin/lib/<edited-files>.ts
```

ESLint scoped to bin/lib runs in <2s. Don't ship orchestrator code that fails this. Local migration runs scope lint to the migrating package's src so they don't catch orchestrator-level violations — the safety net is CI, but failing CI on orchestrator-side lint blocks every migration PR until fixed (we hit this in PR #4943 with empty `catch {}` in `orchestrator-core.ts`).

## Review-response posture (sweep mode)

The orchestrator's `--review-sweep` runs in conversational mode (since 2026-05-08). When new reviewer comments arrive on an open `awaiting_review` PR:

- The agent reads the entire PR thread (top-level reviews, line-level comments, reactions on its own past replies)
- Per comment, the agent decides: **HIGH confidence** → edit code + reply with summary; **MEDIUM confidence** → reply with reasoning + proposal + ask for 👍 confirmation, no code change; **LOW confidence** → reply asking for clarification, no code change
- Reactions and follow-up replies on the agent's proposals advance confidence on the next sweep tick

Protocol details: `docs/migration/PROMPT-review-response.md`. Agent has `gh pr comment`, `gh api .../pulls/<n>/comments` (with `in_reply_to`), and reaction-read tools allowlisted for this. Code commits remain orchestrator-owned; the agent only edits + replies.

When in doubt about a suggestion, the agent should propose (MEDIUM) rather than act (HIGH). False MEDIUM costs one extra sweep tick. False HIGH costs a revert.

## `classes` prop handling per tier (locked 2026-05-11)

Cross-tier audit (`docs/migration/decisions/classes-audit.md`) measured each of the 28 components' `classes` API surface — source-level, internal callsites, external consumer usage (with manual `gh search code` textMatches inspection). Audit data drives the per-tier decision:

- **Tier 0** (Button, Backdrop, Badge, Drawer, Slider, Switch, Tabs): `extends Omit<StandardProps, 'classes'>` + destructure `classes: _classes` runtime backstop. `classes` was broken since the @mui/base step; 0 internal/external real usage. Reference: PR #4947 Button.tsx + ButtonBase.tsx.
- **Tier 0 — Modal**: re-verify. External consumers use `<Modal classes={{ closeButton }}>` per audit §6/§9 — may need Tier 3.b treatment (keep narrowed) instead of standard Tier 0 drop.
- **Tier 1 with vestigial classes** (Container, Typography, Notification): drop via `Omit<StandardProps, 'classes'>` + runtime backstop. Audit-verified vestigial (0 internal, 0 external). Bundle into Tier 1 cleanup PR.
- **Tier 1 — FormControlLabel**: KEEP locally narrowed `classes?: { root?, label? }`. Used internally by Switch/Radio/Checkbox.
- **Tier 1 no `classes` API** (FormLabel, Grid, Form, Note, Menu, FormLayout, ModalContext, Utils): no-op.
- **Tier 2** (Checkbox, Radio, Tooltip, FileInput, Popper): `Omit` drop public. Internal MUI plumbing rewrites with the @base-ui/react migration. Audit-verified 0 external real usage.
- **Tier 3.a** (Accordion, Page): `Omit` drop public. Rewrite internal slot-routing on @base-ui/react parts.
- **Tier 3.b** (Dropdown, OutlinedInput): **KEEP locally narrowed `classes?: { ... }`** (Dropdown: `{ popper, content }`; OutlinedInput: `{ input, root }`). Real external consumers depend on these slots — Dropdown 2 callsites, OutlinedInput 4 callsites.

**Agents migrating any component MUST verify per-component** before applying:
1. Read the source — confirm StandardProps extension / local narrowing / body reads of `classes`.
2. Multiline rg internal callsites.
3. Cross-reference with audit §3/§4/§5.
4. If audit contradicts source state → STOP, update audit doc, don't proceed unilaterally.

PROMPT-light.md §5 + PROMPT-heavy.md §5 codify the research-aware decision matrix. `withClasses` helper from `@toptal/picasso-utils` is deprecated.

**End-state target**: once all 28 components migrate, `StandardProps`, `JssProps`, `Classes` removed from `@toptal/picasso-shared`. Dropdown + OutlinedInput permanently retain their locally narrowed `classes?: { ... }`.

Full audit + per-component data: `docs/migration/decisions/classes-audit.md`. Decision matrix: `docs/migration/decisions/classes-shim.md`.

## Branch hygiene

- **Active orchestrator branch**: `feature/pf-1992-migration-orchestrator` (operator's working branch; worktrees fork from its HEAD)
- **Migration PR target**: `feature/picasso-modernization-temp` (set 2026-05-12). Worktrees still fork from the orchestrator branch's HEAD, so PR diffs may include orchestrator commits not yet on the target.
- **Eventual merge target**: this branch as a whole rolls up into `feature/picasso-modernization` or master after all 28 migrations land
9 changes: 9 additions & 0 deletions bin/lib/agent-mcp-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"_comment": "Claude MCP config loaded by `bin/lib/orchestrator-core.ts` agent.invoke when the orchestrator is run with --with-mcp. The agent gets Playwright MCP tools (browser_navigate, browser_screenshot, browser_console_logs, etc.) for visual verification against a locally-running Storybook (the orchestrator starts Storybook on :9001 before invoking the agent and tears it down on exit). Out-of-the-box Storybook URL pattern: http://localhost:9001/?path=/story/<group>--<story>. See bin/lib/orchestrator-core.ts run() and ORCHESTRATOR.md §Visual feedback for usage.",
"_pinned": "Was `npx -y @playwright/mcp@latest` which forced a 14s network re-resolution on every agent spawn — left the agent's MCP server in `pending` state past the handshake window and the agent ran without playwright tools. Now points at the local devDep binary (0.17s spawn). Bump the version in package.json + here together. The agent-mcp-config.json is read verbatim by claude --mcp-config, so the `cwd` arg here is the absolute repo path resolved from $HOME — works for any worktree because the worktree's `node_modules/.bin/playwright-mcp` is the same package.",
"mcpServers": {
"playwright": {
"command": "node_modules/.bin/playwright-mcp"
}
}
}
Loading
Loading