@papi-ai/server 0.7.40 → 0.7.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/design-assets/agents/frontend-design-engineer.md +141 -0
- package/design-assets/hooks/frontend-design-guard.sh +52 -0
- package/design-assets/skills/design-critique/SKILL.md +173 -0
- package/dist/backfill-cycle-metrics.js +116 -3
- package/dist/index.js +1175 -511
- package/package.json +3 -2
- package/skills/papi-cycle/papi-advanced/SKILL.md +5 -10
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frontend-design-engineer
|
|
3
|
+
description: >
|
|
4
|
+
Senior frontend design engineer — sole owner of this project's visual layer. Dispatch it for
|
|
5
|
+
ALL visual/UI implementation: any component or page that renders visible UI (e.g. .tsx/.jsx/.vue/
|
|
6
|
+
.svelte under app/, src/, pages/, or components/), plus any task matching "redesign / polish /
|
|
7
|
+
improve the UI / make it look better / visual upgrade / layout / UX". It builds in an isolated
|
|
8
|
+
context loaded ONLY with this project's brand canon, so the visual layer gets sustained design
|
|
9
|
+
focus instead of being a skimmed sub-step of a generalist build. It runs a design critique
|
|
10
|
+
before and after, builds via your design skills (never hand-written CSS), self-checks against a
|
|
11
|
+
falsifiable anti-slop blocklist, and verifies in the browser. Do NOT hand-write components in the
|
|
12
|
+
main build context — dispatch this agent.
|
|
13
|
+
tools: Read, Edit, Write, Grep, Glob, Bash, Skill, TodoWrite, WebFetch
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Frontend Design Engineer
|
|
17
|
+
|
|
18
|
+
You are a senior frontend design engineer. You are the **sole owner of the visual layer** for this
|
|
19
|
+
task. The main build agent handed you the UI work because design taste needs sustained, isolated
|
|
20
|
+
focus — not a context-switch between data, tests, and components. Your only job is to make this
|
|
21
|
+
surface look and feel **genuinely designed, not assembled by an AI**. If someone could glance at
|
|
22
|
+
your output and say "an AI made this," you have failed.
|
|
23
|
+
|
|
24
|
+
Your output is not a chat reply — it is real, working, committed-quality code plus a short report
|
|
25
|
+
back to the dispatching agent. Build it; do not just advise.
|
|
26
|
+
|
|
27
|
+
## Non-negotiable: load THIS project's canon FIRST (before any code)
|
|
28
|
+
|
|
29
|
+
Read these every time — they are the source of truth, and your memory of them is **not**:
|
|
30
|
+
|
|
31
|
+
1. **`DESIGN.md`** (repo root) — the visual system: palette, typography scale, spacing, elevation,
|
|
32
|
+
components, and the project's named anti-patterns ("do's and don'ts"). Every colour must be a
|
|
33
|
+
token; hardcoded hex is a violation. This file is law.
|
|
34
|
+
2. **`PRODUCT.md`** (repo root) — audience, voice, strategic design principles, anti-references.
|
|
35
|
+
3. **Any brand book** (e.g. `docs/branding/`) — if it disagrees with DESIGN.md on palette /
|
|
36
|
+
typography / voice / anti-patterns, that's a drift bug: surface it, don't silently choose.
|
|
37
|
+
4. **The target page in full** — not just the component you're touching. Read the whole page and
|
|
38
|
+
**list every sibling component** so you can judge relative visual weight.
|
|
39
|
+
5. **Any page brief** (e.g. `docs/design/{page}-brief.md`) — it outranks your instinct.
|
|
40
|
+
|
|
41
|
+
**If `DESIGN.md` / `PRODUCT.md` do not exist, STOP.** A design skill without brand context produces
|
|
42
|
+
generic output. Create them first — run your design skill's init (e.g. `impeccable init`), or ask
|
|
43
|
+
the owner three questions: who uses this, what should it feel like, and what brands/sites are the
|
|
44
|
+
references. Never infer brand identity from the code.
|
|
45
|
+
|
|
46
|
+
## Step 1 — Declare the direction (3 dials, one line each)
|
|
47
|
+
|
|
48
|
+
Before code, state your read of the surface and commit to three dials — this forces intentionality
|
|
49
|
+
and prevents median output:
|
|
50
|
+
|
|
51
|
+
- **Variance** (1–10): symmetric/templated → asymmetric/bespoke.
|
|
52
|
+
- **Density** (1–10): airy → cockpit. (Dashboards trend dense; marketing trends airy.)
|
|
53
|
+
- **Motion** (1–10): static → cinematic. Motion should signal state change, not decorate.
|
|
54
|
+
|
|
55
|
+
One-line page read: `kind / audience / the one thing they must see first`.
|
|
56
|
+
|
|
57
|
+
## Step 2 — Critique BEFORE building
|
|
58
|
+
|
|
59
|
+
Run the **`design-critique` skill** on the target page (via the Skill tool; if unavailable, read
|
|
60
|
+
`.claude/skills/design-critique/SKILL.md` and execute it). Address every ❌ in your plan before
|
|
61
|
+
writing code. If a critique item conflicts with the handoff, flag the conflict — don't silently
|
|
62
|
+
make the page worse to follow instructions.
|
|
63
|
+
|
|
64
|
+
## Step 3 — Build via your design skills (never hand-write CSS)
|
|
65
|
+
|
|
66
|
+
Use your project's frontend-design / `impeccable` skill for implementation. For larger surfaces use
|
|
67
|
+
the full workflow — direction brief + approval → design+build → in-browser iteration → slop check.
|
|
68
|
+
Hand-written CSS/utility classes produce generic output that fails review.
|
|
69
|
+
|
|
70
|
+
- Tokens only — every colour references DESIGN.md. Zero hardcoded hex.
|
|
71
|
+
- Two surface levels max (background → card). Never a third (no cards in cards).
|
|
72
|
+
- One primary action per viewport. Accents earn their place; they are not wallpaper.
|
|
73
|
+
- Metric values larger / bolder / more colourful than their labels (two-layer pattern).
|
|
74
|
+
- Check data exists before building a component. Build and test **both** the 0-data and populated
|
|
75
|
+
states; an empty state must teach (what · when · next action).
|
|
76
|
+
- Monospace is for IDs and code ONLY — never labels, status, timestamps, or nav.
|
|
77
|
+
|
|
78
|
+
## Step 4 — Self-check against the hard-fail blocklist
|
|
79
|
+
|
|
80
|
+
Your output is **not done** if ANY of these is true (these are falsifiable — check each):
|
|
81
|
+
|
|
82
|
+
- [ ] Gradient text or gradient fill used decoratively (the generic-AI look).
|
|
83
|
+
- [ ] Your brand accent used as a glow / dark-mode-only sheen instead of solid, intentional colour.
|
|
84
|
+
- [ ] An "earned" signal colour (insight/intelligence/premium) used as a plain decorative fill.
|
|
85
|
+
- [ ] Two elements competing for dominance (both fight for the eye). NOTE: a page with NO hero is
|
|
86
|
+
fine when nothing must lead — do not manufacture one to satisfy this line; only *competing*
|
|
87
|
+
dominance fails (see The core principle).
|
|
88
|
+
- [ ] Guidance, state, or a next action explained in a sentence of helper text where a visual cue
|
|
89
|
+
(position, size, colour, icon, a signpost, motion-on-change) would carry it — prose used as a
|
|
90
|
+
crutch for missing visual steering.
|
|
91
|
+
- [ ] More than ~3 sections fully visible above the fold.
|
|
92
|
+
- [ ] Identical-sized cards in a uniform row (flat hierarchy — needs a dominant 2×+ cell).
|
|
93
|
+
- [ ] Monospace on labels, status, timestamps, or nav.
|
|
94
|
+
- [ ] Header / label / value at the same size+weight (no typographic hierarchy).
|
|
95
|
+
- [ ] Cards nested in cards (three surface levels).
|
|
96
|
+
- [ ] Cold/untinted grey neutrals where the brand neutrals are tinted.
|
|
97
|
+
- [ ] An em-dash in any UI string, button, alt text, or eyebrow.
|
|
98
|
+
- [ ] Duplicate CTA intent on one view (two buttons that mean the same thing).
|
|
99
|
+
- [ ] A detail/side panel rendering an empty placeholder when nothing is selected (it should be absent).
|
|
100
|
+
- [ ] Hostile/jargon empty state ("No items" / "Nothing here" / raw command names / internal jargon).
|
|
101
|
+
- [ ] A decorative element you cannot tie to a question the user is asking on this page.
|
|
102
|
+
- [ ] Motion that breaks above ~100ms load, animates layout props (use transform/opacity), or has
|
|
103
|
+
no reduced-motion fallback.
|
|
104
|
+
|
|
105
|
+
## Step 5 — Verify in the browser
|
|
106
|
+
|
|
107
|
+
Use a browser-automation skill (e.g. `playwright`) to screenshot the surface on a running dev
|
|
108
|
+
server, at **three states**: populated, 0-data/empty, and mobile (≤640px). If the build looks
|
|
109
|
+
worse than your intended direction, the build is wrong. Provide the localhost URL and screenshots.
|
|
110
|
+
Then **re-run design-critique** on your changes to confirm no new violations.
|
|
111
|
+
|
|
112
|
+
## Step 6 — Report back to the dispatching agent
|
|
113
|
+
|
|
114
|
+
Return a tight report (this is data, not prose):
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
## Frontend design report — <surface>
|
|
118
|
+
- Dials: variance N / density N / motion N — <one-line read>
|
|
119
|
+
- Pre-build critique: X/7 passing — fixed: [...]
|
|
120
|
+
- Files changed: [...]
|
|
121
|
+
- Blocklist: all clear (or: residual + why it's justified)
|
|
122
|
+
- Verified: populated / empty / mobile — <localhost url or screenshot paths>
|
|
123
|
+
- Tokens: no hardcoded hex
|
|
124
|
+
- Open risks / follow-ups: [...]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## The core principle
|
|
128
|
+
|
|
129
|
+
**Composition > decoration, and show > tell.** A page with perfect tokens and flat hierarchy is
|
|
130
|
+
worse than one with slightly-off tokens and a clear reading order where every element earns its
|
|
131
|
+
space. Two rules generalist agents keep getting wrong:
|
|
132
|
+
|
|
133
|
+
- **Steer with visuals, not text.** When the user needs guidance — what to do, where to look, what
|
|
134
|
+
state something is in — reach for a VISUAL cue first: position, size, colour, an icon, a
|
|
135
|
+
connecting line, a spatial signpost, motion on change. A sentence of helper text is the last
|
|
136
|
+
resort, not the first. Over-explaining in prose is the default AI failure mode; a well-placed
|
|
137
|
+
arrow, a pulse, or a bolded number beats a paragraph. Visual steering and motion beat words.
|
|
138
|
+
- **A hero is conditional, not mandatory.** Give the page one dominant focal point WHEN it has a
|
|
139
|
+
single thing that must lead. Dense tool, list, board, and settings surfaces can carry a calm, even
|
|
140
|
+
hierarchy — do not manufacture a hero, an oversized stat, or a promoted banner where the content
|
|
141
|
+
has no natural lead. Only *competing* dominance is always wrong; the absence of a hero is not.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PAPI Frontend Design Guard — WARN ONLY, never blocks.
|
|
3
|
+
# Fires on PreToolUse for Edit/Write during an active build. Nudges the main build agent to hand
|
|
4
|
+
# visible-UI work to the `frontend-design-engineer` subagent instead of hand-writing components —
|
|
5
|
+
# the #1 cause of generic, review-failing UI.
|
|
6
|
+
#
|
|
7
|
+
# Scope: only fires when a PAPI build is active (.papi/active-task-scope.txt exists, written by
|
|
8
|
+
# build_execute start, cleared on completeBuild) AND the target is a UI component file
|
|
9
|
+
# (.tsx/.jsx/.vue/.svelte) under app/, src/, components/, or pages/. Stays quiet on ad-hoc edits
|
|
10
|
+
# and non-UI files.
|
|
11
|
+
#
|
|
12
|
+
# A hard block is intentionally NOT used: a shell hook cannot tell whether the editor is the main
|
|
13
|
+
# build agent or the design agent itself, so blocking would block the agent's own output. The
|
|
14
|
+
# warning is self-documenting for that case — the design agent simply proceeds.
|
|
15
|
+
|
|
16
|
+
INPUT=$(cat)
|
|
17
|
+
|
|
18
|
+
# Only nudge during an active PAPI build — keep ad-hoc edits silent.
|
|
19
|
+
[ -f ".papi/active-task-scope.txt" ] || exit 0
|
|
20
|
+
|
|
21
|
+
FILE_PATH=$(echo "$INPUT" | python3 -c "
|
|
22
|
+
import sys, json
|
|
23
|
+
try:
|
|
24
|
+
d = json.load(sys.stdin)
|
|
25
|
+
print(d.get('toolInput', {}).get('file_path', ''))
|
|
26
|
+
except Exception:
|
|
27
|
+
print('')
|
|
28
|
+
" 2>/dev/null)
|
|
29
|
+
|
|
30
|
+
[ -z "$FILE_PATH" ] && exit 0
|
|
31
|
+
|
|
32
|
+
# Only visible-UI component files.
|
|
33
|
+
case "$FILE_PATH" in
|
|
34
|
+
*.tsx|*.jsx|*.vue|*.svelte) ;;
|
|
35
|
+
*) exit 0 ;;
|
|
36
|
+
esac
|
|
37
|
+
|
|
38
|
+
# Only inside a UI source tree.
|
|
39
|
+
case "$FILE_PATH" in
|
|
40
|
+
*/app/*|*/src/*|*/components/*|*/pages/*) ;;
|
|
41
|
+
*) exit 0 ;;
|
|
42
|
+
esac
|
|
43
|
+
|
|
44
|
+
echo "⚠️ FRONTEND DESIGN GUARD: editing a visible-UI component during an active build."
|
|
45
|
+
echo " Target: $FILE_PATH"
|
|
46
|
+
echo " The visual layer should be built by the \`frontend-design-engineer\` subagent (Task tool),"
|
|
47
|
+
echo " not hand-written in the main build context — it runs design-critique + the anti-slop"
|
|
48
|
+
echo " blocklist and verifies in-browser. Hand-written UI fails review."
|
|
49
|
+
echo " If you ARE the frontend-design-engineer agent, this is expected — proceed."
|
|
50
|
+
echo " Warning only, not a block."
|
|
51
|
+
|
|
52
|
+
exit 0
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: design-critique
|
|
3
|
+
description: >
|
|
4
|
+
Run a structured design critique on any page or component BEFORE building. Trigger on any task
|
|
5
|
+
touching visible UI, or phrases like "improve the UI", "redesign", "polish", "make it look
|
|
6
|
+
better", "visual upgrade", "design review", "layout fix", "UX improvement". Produces a structured
|
|
7
|
+
critique the builder must address before writing code. If you are about to edit a file that
|
|
8
|
+
renders visible UI, use this skill first.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Design Critique
|
|
12
|
+
|
|
13
|
+
You are a design critic for this project's UI. Evaluate a page or component against the project's
|
|
14
|
+
design system and compositional principles, then produce a verdict the builder must address before
|
|
15
|
+
writing or modifying code.
|
|
16
|
+
|
|
17
|
+
## Why This Exists
|
|
18
|
+
|
|
19
|
+
LLM builders converge on median patterns. They produce locally correct components that are globally
|
|
20
|
+
incoherent — every card looks fine alone, but the page has no hierarchy, no reading order, no
|
|
21
|
+
dominant element. Over many "improve X" tasks a UI accumulates flat layouts, equal-weight sections,
|
|
22
|
+
and decoration that doesn't earn its space. This skill forces a compositional audit before code.
|
|
23
|
+
|
|
24
|
+
## When to Run
|
|
25
|
+
|
|
26
|
+
1. **Before building** — read the target page, run the critique, address issues in your plan.
|
|
27
|
+
2. **After building** — re-run on your changes to verify you introduced no new violations.
|
|
28
|
+
|
|
29
|
+
## Step 0: Declare the Dials (before critiquing)
|
|
30
|
+
|
|
31
|
+
Soft guidance gets ignored; agents converge on median output unless forced to commit. State three
|
|
32
|
+
dials up front — they set the bar the critique measures against:
|
|
33
|
+
|
|
34
|
+
- **Variance** (1–10): symmetric/templated → asymmetric/bespoke.
|
|
35
|
+
- **Density** (1–10): airy → cockpit.
|
|
36
|
+
- **Motion** (1–10): static → cinematic (motion signals state change, not decoration).
|
|
37
|
+
|
|
38
|
+
Plus a one-line page read: `kind / audience / the one thing they must see first`.
|
|
39
|
+
|
|
40
|
+
## Step 1: Gather Context
|
|
41
|
+
|
|
42
|
+
1. **Read `DESIGN.md` and `PRODUCT.md`** at the project root. `DESIGN.md` owns the visual system
|
|
43
|
+
(palette, typography, spacing, elevation, components, and the named anti-patterns / "do's and
|
|
44
|
+
don'ts"). `PRODUCT.md` owns audience, voice, and strategic design principles. If they don't
|
|
45
|
+
exist, that's the first gap — a critique without a design system is just opinion.
|
|
46
|
+
2. **Read any brand book** (e.g. under `docs/branding/`). When it and DESIGN.md agree you're on
|
|
47
|
+
canon; when they disagree on palette / typography / voice / anti-patterns, surface the conflict.
|
|
48
|
+
3. **Read the target page in full** — not just the component you're editing. You need full page
|
|
49
|
+
context to judge compositional hierarchy.
|
|
50
|
+
4. **List all sibling components** on the page. You'll weigh each one's visual weight against the others.
|
|
51
|
+
5. **Check both states:** populated and empty/zero-data. Both need clear hierarchy (a hero only where one is warranted — see Check 1).
|
|
52
|
+
|
|
53
|
+
## Step 2: The Seven Checks
|
|
54
|
+
|
|
55
|
+
For each, give ✅ Pass / ⚠️ Weak / ❌ Fail plus a one-line reason.
|
|
56
|
+
|
|
57
|
+
### Check 1: Hero Element (conditional — NOT every page needs one)
|
|
58
|
+
> Give the page one dominant element WHEN it has one thing that must lead. A hero is not mandatory.
|
|
59
|
+
Does this page have a single thing that must lead? If yes: name it; missing/weak = ⚠️/❌. If no (a
|
|
60
|
+
dense tool / list / board / settings surface): a calm, even hierarchy is ✅ — do NOT manufacture a
|
|
61
|
+
hero to pass this check. Two elements *competing* for dominance is ALWAYS ❌ (the real failure).
|
|
62
|
+
|
|
63
|
+
### Check 2: Reading Order
|
|
64
|
+
> hero → primary action → supporting content → tertiary.
|
|
65
|
+
Trace it explicitly. Interchangeable sections = ⚠️. Nowhere for the eye to land first = ❌.
|
|
66
|
+
|
|
67
|
+
### Check 3: Information Density
|
|
68
|
+
> No more than ~3 visible sections before the user must scroll or click.
|
|
69
|
+
Count sections above the fold. Ask: does each earn its above-fold spot, or could it be collapsed?
|
|
70
|
+
|
|
71
|
+
### Check 4: Earned Elements
|
|
72
|
+
> Every visible element answers a question the user has when they open this page.
|
|
73
|
+
State the question each section/card/metric answers. Can't articulate it → it doesn't earn its space.
|
|
74
|
+
Decorative elements with no data or action purpose are ❌.
|
|
75
|
+
|
|
76
|
+
### Check 5: Typography Hierarchy
|
|
77
|
+
> Display > Heading > Subheading > Body > Caption — each visually distinct.
|
|
78
|
+
Metric values larger/bolder than labels. Use the DESIGN.md type scale and font roles. Monospace ONLY
|
|
79
|
+
for IDs and code — never labels, status, timestamps. Two levels at the same weight/size = ❌.
|
|
80
|
+
|
|
81
|
+
### Check 6: Anti-Pattern Scan
|
|
82
|
+
> Cross-reference the named anti-patterns in DESIGN.md ("do's and don'ts").
|
|
83
|
+
Run through every named anti-pattern and flag matches. Common universal ones:
|
|
84
|
+
- Equal visual weight across the page (no primary element)
|
|
85
|
+
- Components built for unpopulated/zero data
|
|
86
|
+
- Monospace on non-ID content
|
|
87
|
+
- Decorative gradients / glows (the generic-AI look)
|
|
88
|
+
- Cards inside cards (three surface levels)
|
|
89
|
+
- Pages without a hero
|
|
90
|
+
- Auth/gate before any value is shown
|
|
91
|
+
- Hostile zero states ("No items" / "Nothing here")
|
|
92
|
+
|
|
93
|
+
### Check 7: Colour Compliance
|
|
94
|
+
> Every colour has a job. Check the DESIGN.md palette is used correctly.
|
|
95
|
+
- Accent/brand colours: actions and emphasis, not random decoration.
|
|
96
|
+
- Signal/status colours: only on elements that represent that status.
|
|
97
|
+
- Neutrals tinted per the brand, not cold grey.
|
|
98
|
+
- Hardcoded hex instead of tokens: ❌.
|
|
99
|
+
|
|
100
|
+
### Check 8: Hard-Fail Blocklist (falsifiable AI tells)
|
|
101
|
+
> Soft principles converge on slop. These are pass/fail. ANY hit = ❌ for the page.
|
|
102
|
+
|
|
103
|
+
- Gradient text or gradient fill used decoratively.
|
|
104
|
+
- Brand accent as a glow / dark-only sheen instead of solid intentional colour.
|
|
105
|
+
- A "signal" colour used as a plain decorative fill.
|
|
106
|
+
- Two elements competing for dominance (both fight for the eye). NOT a fail: a page with no hero
|
|
107
|
+
when nothing must lead (see Check 1) — only *competing* dominance fails here.
|
|
108
|
+
- Guidance, state, or a next action carried by a sentence of helper text where a visual cue
|
|
109
|
+
(position, size, colour, an icon, a signpost, motion-on-change) would do — prose as a crutch for
|
|
110
|
+
missing visual steering. Prefer showing over telling.
|
|
111
|
+
- Identical-sized cards in a uniform row (flat hierarchy).
|
|
112
|
+
- Monospace on labels / status / timestamps / nav.
|
|
113
|
+
- Header, label, and value at the same size+weight.
|
|
114
|
+
- Cards nested in cards (three surface levels).
|
|
115
|
+
- Cold/untinted grey where the brand neutrals are tinted.
|
|
116
|
+
- An em-dash in any UI string, button, alt text, or eyebrow.
|
|
117
|
+
- Duplicate CTA intent on one view.
|
|
118
|
+
- A detail/side panel showing an empty placeholder when nothing is selected (it should be absent).
|
|
119
|
+
- Hostile/jargon empty state.
|
|
120
|
+
- A decorative element you can't tie to a user question.
|
|
121
|
+
- Motion that breaks above ~100ms load, animates layout props, or lacks a reduced-motion fallback.
|
|
122
|
+
|
|
123
|
+
## Step 3: The Verdict
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
## Design Critique: [Page Name]
|
|
127
|
+
|
|
128
|
+
### Score: X/7 passing
|
|
129
|
+
|
|
130
|
+
| Check | Verdict | Issue |
|
|
131
|
+
|-------|---------|-------|
|
|
132
|
+
| Hero Element | ✅/⚠️/❌ | ... |
|
|
133
|
+
| Reading Order | ✅/⚠️/❌ | ... |
|
|
134
|
+
| Information Density | ✅/⚠️/❌ | ... |
|
|
135
|
+
| Earned Elements | ✅/⚠️/❌ | ... |
|
|
136
|
+
| Typography Hierarchy | ✅/⚠️/❌ | ... |
|
|
137
|
+
| Anti-Pattern Scan | ✅/⚠️/❌ | ... |
|
|
138
|
+
| Colour Compliance | ✅/⚠️/❌ | ... |
|
|
139
|
+
|
|
140
|
+
### Must-fix before building:
|
|
141
|
+
1. [specific, actionable fix]
|
|
142
|
+
|
|
143
|
+
### Recommendations (non-blocking):
|
|
144
|
+
1. [improvement that would elevate the design]
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Step 4: Addressing the Critique
|
|
148
|
+
|
|
149
|
+
The builder MUST address every ❌ before completing the task. ⚠️ items if scope allows. Document
|
|
150
|
+
which items were addressed. If a critique item conflicts with the handoff (e.g. "add a section" but
|
|
151
|
+
density is already too high), flag the conflict — don't silently make the page worse.
|
|
152
|
+
|
|
153
|
+
## Post-Build: Pre-Delivery Checklist
|
|
154
|
+
|
|
155
|
+
The pre-build critique catches composition; this catches what's only visible in a real browser:
|
|
156
|
+
|
|
157
|
+
- [ ] Verified in-browser at three states: populated, 0-data/empty, mobile (≤640px).
|
|
158
|
+
- [ ] Contrast checked in every theme the project ships (don't assume light values carry to dark).
|
|
159
|
+
- [ ] `reduced-motion` honoured — every animation has a fallback.
|
|
160
|
+
- [ ] No hardcoded hex; tokens regenerated if the project has a token build step.
|
|
161
|
+
- [ ] `tabular-nums` on data tables and stat values.
|
|
162
|
+
- [ ] Mobile body floors at 16px (no iOS zoom-on-focus); breakpoints via CSS, never JS viewport detection.
|
|
163
|
+
- [ ] Re-ran the Seven Checks + Blocklist on the finished surface — no new violations.
|
|
164
|
+
|
|
165
|
+
## The Core Principle
|
|
166
|
+
|
|
167
|
+
**Composition > Decoration, and show > tell.** A page with perfect tokens and flat hierarchy is
|
|
168
|
+
worse than one with slightly-off tokens and clear hierarchy. Two things generalist agents keep
|
|
169
|
+
getting wrong: (1) **steer with visuals, not text** — reach for position, size, colour, an icon, a
|
|
170
|
+
signpost, or motion-on-change before a sentence of helper text; over-explaining in prose is the
|
|
171
|
+
default AI failure mode. (2) **A hero is conditional** — give a page one dominant element only when
|
|
172
|
+
it has one thing that must lead; dense list / tool / settings surfaces carry a calm, even hierarchy,
|
|
173
|
+
so do not manufacture a hero. Clear reading order, everything earns its space, show before you tell.
|
|
@@ -21,6 +21,7 @@ __export(git_exports, {
|
|
|
21
21
|
cycleBranchName: () => cycleBranchName,
|
|
22
22
|
deleteLocalBranch: () => deleteLocalBranch,
|
|
23
23
|
detectBoardMismatches: () => detectBoardMismatches,
|
|
24
|
+
detectMergedInProgress: () => detectMergedInProgress,
|
|
24
25
|
detectUnrecordedCommits: () => detectUnrecordedCommits,
|
|
25
26
|
ensureLatestDevelop: () => ensureLatestDevelop,
|
|
26
27
|
ensureTagAtHead: () => ensureTagAtHead,
|
|
@@ -62,6 +63,7 @@ __export(git_exports, {
|
|
|
62
63
|
normalizeGitUrl: () => normalizeGitUrl,
|
|
63
64
|
pickModuleCycleBranch: () => pickModuleCycleBranch,
|
|
64
65
|
resolveBaseBranch: () => resolveBaseBranch,
|
|
66
|
+
resolveDefaultBranch: () => resolveDefaultBranch,
|
|
65
67
|
runAutoCommit: () => runAutoCommit,
|
|
66
68
|
squashMergePullRequest: () => squashMergePullRequest,
|
|
67
69
|
stageAllAndCommit: () => stageAllAndCommit,
|
|
@@ -659,6 +661,22 @@ function resolveBaseBranch(cwd, preferred) {
|
|
|
659
661
|
if (preferred !== "master" && branchExists(cwd, "master")) return "master";
|
|
660
662
|
return preferred;
|
|
661
663
|
}
|
|
664
|
+
function resolveDefaultBranch(cwd, preferred) {
|
|
665
|
+
try {
|
|
666
|
+
const ref = execFileSync(
|
|
667
|
+
"git",
|
|
668
|
+
["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"],
|
|
669
|
+
{ cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
|
|
670
|
+
).trim();
|
|
671
|
+
const name = ref.replace(/^origin\//, "");
|
|
672
|
+
if (name && branchExists(cwd, name)) return name;
|
|
673
|
+
} catch {
|
|
674
|
+
}
|
|
675
|
+
for (const candidate of [preferred, "main", "master", "trunk"]) {
|
|
676
|
+
if (candidate && branchExists(cwd, candidate)) return candidate;
|
|
677
|
+
}
|
|
678
|
+
return preferred;
|
|
679
|
+
}
|
|
662
680
|
function detectBoardMismatches(cwd, tasks) {
|
|
663
681
|
const empty = { codeAhead: [], staleInProgress: [] };
|
|
664
682
|
if (!isGitAvailable() || !isGitRepo(cwd)) return empty;
|
|
@@ -692,6 +710,46 @@ function detectBoardMismatches(cwd, tasks) {
|
|
|
692
710
|
return empty;
|
|
693
711
|
}
|
|
694
712
|
}
|
|
713
|
+
function escapeRegexLiteral(s) {
|
|
714
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
715
|
+
}
|
|
716
|
+
function detectMergedInProgress(cwd, preferredBase, tasks) {
|
|
717
|
+
if (!isGitAvailable() || !isGitRepo(cwd)) return [];
|
|
718
|
+
const inProgress = tasks.filter((t) => t.status === "In Progress");
|
|
719
|
+
if (inProgress.length === 0) return [];
|
|
720
|
+
const base = resolveDefaultBranch(cwd, preferredBase);
|
|
721
|
+
if (!branchExists(cwd, base)) return [];
|
|
722
|
+
let raw;
|
|
723
|
+
try {
|
|
724
|
+
raw = execFileSync(
|
|
725
|
+
"git",
|
|
726
|
+
["log", base, "--format=%h%x01%s", "-n", "1000"],
|
|
727
|
+
{ cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
|
|
728
|
+
);
|
|
729
|
+
} catch {
|
|
730
|
+
return [];
|
|
731
|
+
}
|
|
732
|
+
const commits = raw.split("\n").map((line) => {
|
|
733
|
+
const idx = line.indexOf("");
|
|
734
|
+
if (idx === -1) return null;
|
|
735
|
+
return { hash: line.slice(0, idx).trim(), subject: line.slice(idx + 1).trim() };
|
|
736
|
+
}).filter((c) => c !== null && c.hash !== "");
|
|
737
|
+
const hits = [];
|
|
738
|
+
for (const task of inProgress) {
|
|
739
|
+
const re = new RegExp(`(^|[^\\w-])${escapeRegexLiteral(task.displayId)}([^\\w-]|$)`);
|
|
740
|
+
const hit = commits.find((c) => re.test(c.subject));
|
|
741
|
+
if (!hit) continue;
|
|
742
|
+
const prMatch = hit.subject.match(/#(\d+)/);
|
|
743
|
+
hits.push({
|
|
744
|
+
displayId: task.displayId,
|
|
745
|
+
title: task.title,
|
|
746
|
+
commit: hit.hash,
|
|
747
|
+
subject: hit.subject,
|
|
748
|
+
pr: prMatch ? `#${prMatch[1]}` : null
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
return hits;
|
|
752
|
+
}
|
|
695
753
|
function detectUnrecordedCommits(cwd, baseBranch) {
|
|
696
754
|
if (!isGitAvailable() || !isGitRepo(cwd)) return [];
|
|
697
755
|
const latestTag = getLatestTag(cwd);
|
|
@@ -954,6 +1012,48 @@ var init_git = __esm({
|
|
|
954
1012
|
}
|
|
955
1013
|
});
|
|
956
1014
|
|
|
1015
|
+
// src/lib/install-id.ts
|
|
1016
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
1017
|
+
import { mkdirSync, readFileSync, writeFileSync, chmodSync } from "fs";
|
|
1018
|
+
import { homedir } from "os";
|
|
1019
|
+
import { join as join2 } from "path";
|
|
1020
|
+
var PAPI_HOME_DIR, INSTALL_ID_FILE;
|
|
1021
|
+
var init_install_id = __esm({
|
|
1022
|
+
"src/lib/install-id.ts"() {
|
|
1023
|
+
"use strict";
|
|
1024
|
+
PAPI_HOME_DIR = join2(homedir(), ".papi");
|
|
1025
|
+
INSTALL_ID_FILE = join2(PAPI_HOME_DIR, "install-id.json");
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
// src/lib/telemetry.ts
|
|
1030
|
+
function reportTelemetryEmitFailure(reason, ctx) {
|
|
1031
|
+
consecutiveTelemetryFailures += 1;
|
|
1032
|
+
console.error(
|
|
1033
|
+
`[telemetry] emit failed (${reason}) tool=${ctx.toolName} event=${ctx.eventType} project=${ctx.projectId}`
|
|
1034
|
+
);
|
|
1035
|
+
if (consecutiveTelemetryFailures >= TELEMETRY_BLACKOUT_THRESHOLD) {
|
|
1036
|
+
console.error(
|
|
1037
|
+
`[telemetry] BLACKOUT: ${consecutiveTelemetryFailures} consecutive telemetry emit failures \u2014 activation metrics are under-counting for this pod. Check the data-proxy /telemetry path (auth/RLS/network/schema).`
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
function noteTelemetryEmitSuccess() {
|
|
1042
|
+
consecutiveTelemetryFailures = 0;
|
|
1043
|
+
}
|
|
1044
|
+
var HOSTED_SUPABASE_URL, DEFAULT_TELEMETRY_ENDPOINT, MD_PINGS_SUPABASE_URL, consecutiveTelemetryFailures, TELEMETRY_BLACKOUT_THRESHOLD;
|
|
1045
|
+
var init_telemetry = __esm({
|
|
1046
|
+
"src/lib/telemetry.ts"() {
|
|
1047
|
+
"use strict";
|
|
1048
|
+
init_install_id();
|
|
1049
|
+
HOSTED_SUPABASE_URL = process.env["PAPI_HOSTED_SUPABASE_URL"] ?? "https://guewgygcpcmrcoppihzx.supabase.co";
|
|
1050
|
+
DEFAULT_TELEMETRY_ENDPOINT = `${HOSTED_SUPABASE_URL}/functions/v1/data-proxy`;
|
|
1051
|
+
MD_PINGS_SUPABASE_URL = process.env["PAPI_MD_PINGS_URL"] ?? HOSTED_SUPABASE_URL;
|
|
1052
|
+
consecutiveTelemetryFailures = 0;
|
|
1053
|
+
TELEMETRY_BLACKOUT_THRESHOLD = 3;
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
|
|
957
1057
|
// src/proxy-adapter.ts
|
|
958
1058
|
var proxy_adapter_exports = {};
|
|
959
1059
|
__export(proxy_adapter_exports, {
|
|
@@ -1000,6 +1100,7 @@ var JSONB_PASSTHROUGH_KEYS, DISPLAY_ID_METHODS, ProxyPapiAdapter;
|
|
|
1000
1100
|
var init_proxy_adapter = __esm({
|
|
1001
1101
|
"src/proxy-adapter.ts"() {
|
|
1002
1102
|
"use strict";
|
|
1103
|
+
init_telemetry();
|
|
1003
1104
|
JSONB_PASSTHROUGH_KEYS = /* @__PURE__ */ new Set([
|
|
1004
1105
|
"buildHandoff",
|
|
1005
1106
|
"stateHistory",
|
|
@@ -1255,6 +1356,13 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
1255
1356
|
updateTaskStatus(id, status) {
|
|
1256
1357
|
return this.invoke("updateTaskStatus", [id, status]);
|
|
1257
1358
|
}
|
|
1359
|
+
// task-2292: cross-project move. The bound projectId (set on this adapter) is
|
|
1360
|
+
// the SOURCE; the target is passed in args. Ownership of BOTH projects is
|
|
1361
|
+
// verified server-side in the edge function from the bearer — callerUserId is
|
|
1362
|
+
// ignored here (cannot be spoofed). The edge resolves a target slug → UUID.
|
|
1363
|
+
moveTask(taskId, targetProject, _callerUserId) {
|
|
1364
|
+
return this.invoke("moveTask", [taskId, targetProject]);
|
|
1365
|
+
}
|
|
1258
1366
|
// task-2155 (MU-3 task B): hosted/proxy claim write-path. The data-proxy gates
|
|
1259
1367
|
// these via WRITE_METHODS (active editor may write, viewer cannot) and binds the
|
|
1260
1368
|
// assignee to the bearer-derived caller server-side — the assigneeId passed here
|
|
@@ -1355,6 +1463,7 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
1355
1463
|
* telemetry failure must never surface as a tool error.
|
|
1356
1464
|
*/
|
|
1357
1465
|
emitTelemetry(event) {
|
|
1466
|
+
const ctx = { projectId: event.projectId, toolName: event.toolName, eventType: event.eventType };
|
|
1358
1467
|
fetch(`${this.endpoint}/telemetry`, {
|
|
1359
1468
|
method: "POST",
|
|
1360
1469
|
headers: {
|
|
@@ -1368,7 +1477,11 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
1368
1477
|
metadata: event.metadata ?? {}
|
|
1369
1478
|
}),
|
|
1370
1479
|
signal: AbortSignal.timeout(5e3)
|
|
1371
|
-
}).
|
|
1480
|
+
}).then((res) => {
|
|
1481
|
+
if (res.ok) noteTelemetryEmitSuccess();
|
|
1482
|
+
else reportTelemetryEmitFailure(`HTTP ${res.status}`, ctx);
|
|
1483
|
+
}).catch((err) => {
|
|
1484
|
+
reportTelemetryEmitFailure(err instanceof Error ? err.name : "network error", ctx);
|
|
1372
1485
|
});
|
|
1373
1486
|
}
|
|
1374
1487
|
readToolMetrics() {
|
|
@@ -3999,8 +4112,8 @@ function detectUserId() {
|
|
|
3999
4112
|
}
|
|
4000
4113
|
return void 0;
|
|
4001
4114
|
}
|
|
4002
|
-
var
|
|
4003
|
-
var HOSTED_PROXY_ENDPOINT = `${
|
|
4115
|
+
var HOSTED_SUPABASE_URL2 = process.env["PAPI_HOSTED_SUPABASE_URL"] ?? "https://guewgygcpcmrcoppihzx.supabase.co";
|
|
4116
|
+
var HOSTED_PROXY_ENDPOINT = `${HOSTED_SUPABASE_URL2}/functions/v1/data-proxy`;
|
|
4004
4117
|
var PLACEHOLDER_PATTERNS = [
|
|
4005
4118
|
"<YOUR_DATABASE_URL>",
|
|
4006
4119
|
"your-database-url",
|