Merge chore/backmerge-release-full into develop

This commit is contained in:
Bastien Chanot
2026-07-08 18:05:18 +02:00
14 changed files with 1532 additions and 529 deletions
+2
View File
@@ -368,3 +368,5 @@ rules:
- Adversarial review of the whole 9-job series (release/1.0.0..develop) → `.audit/review-release-1.0.0.md`: 1 BLOQUANT + 5 à corriger + 5 mineurs, 10 verified false-positives. 2 sub-agent verdicts overturned (job7 gitleaks hook inert [[LRN-114]], contract tool-grant FP [[LRN-115]]). Jobs 4/5/6/8 CLEAN, validator-analyzer contract SOUND. J4-16 follow-up above CLOSED: trailer twins found in bugfixer/feater/hotfixer. - Adversarial review of the whole 9-job series (release/1.0.0..develop) → `.audit/review-release-1.0.0.md`: 1 BLOQUANT + 5 à corriger + 5 mineurs, 10 verified false-positives. 2 sub-agent verdicts overturned (job7 gitleaks hook inert [[LRN-114]], contract tool-grant FP [[LRN-115]]). Jobs 4/5/6/8 CLEAN, validator-analyzer contract SOUND. J4-16 follow-up above CLOSED: trailer twins found in bugfixer/feater/hotfixer.
- Remediation `chore/review-remediation` (unmerged, human gate): A1 trailer purge (3 templates) + whole-surface sweep; A2 gitleaks hook re-installed (`install-hook`) + negative-secret gate proven; A4 strict-YAML quote (seo/security-auditor); A5 geo own-policy (user-approved, PERMISSIVE default kept, false CLAUDE.md attribution dropped); A8 path-b PROVEN — /seo+/geo AUTO items land on disk via L1 (no silent no-op); fil-rouge `lib/tests/run-review-guards.sh` (5 guards, teeth-verified); A3 backfill LRN-098/101 + EVAL-015 + BLK-016 + PORTED rtk fix e58037c (was live-broken on develop, ~460K tokens/30d); A6 guard 280→320 + [[BDR-062]] (supersede BDR-031's 275 target). make test GREEN throughout. - Remediation `chore/review-remediation` (unmerged, human gate): A1 trailer purge (3 templates) + whole-surface sweep; A2 gitleaks hook re-installed (`install-hook`) + negative-secret gate proven; A4 strict-YAML quote (seo/security-auditor); A5 geo own-policy (user-approved, PERMISSIVE default kept, false CLAUDE.md attribution dropped); A8 path-b PROVEN — /seo+/geo AUTO items land on disk via L1 (no silent no-op); fil-rouge `lib/tests/run-review-guards.sh` (5 guards, teeth-verified); A3 backfill LRN-098/101 + EVAL-015 + BLK-016 + PORTED rtk fix e58037c (was live-broken on develop, ~460K tokens/30d); A6 guard 280→320 + [[BDR-062]] (supersede BDR-031's 275 target). make test GREEN throughout.
- Capitalized: [[LRN-113]] partial-fix+guard (structural), [[LRN-114]] hook-drift, [[LRN-115]] analyzer report-grants (FP1), [[LRN-116]] release fix missing from develop, [[BDR-062]] density realign, [[EVAL-021]] the review, [[EVAL-022]] M5 pins trace. Noted un-back-merged release chores beyond A3: e65796f (SC1091 lint silence) — left for a future reconcile. - Capitalized: [[LRN-113]] partial-fix+guard (structural), [[LRN-114]] hook-drift, [[LRN-115]] analyzer report-grants (FP1), [[LRN-116]] release fix missing from develop, [[BDR-062]] density realign, [[EVAL-021]] the review, [[EVAL-022]] M5 pins trace. Noted un-back-merged release chores beyond A3: e65796f (SC1091 lint silence) — left for a future reconcile.
- Full back-merge release/1.0.0→develop (`chore/backmerge-release-full`, unmerged): the RC fork had left ~6 functional fixes orphaned on develop, silently. PORTED via cherry-pick, make test green each: `095d881` drop find-skills, `a1093ca` make-update TTY-guard (proven: EOF-die exit1 → guarded exit0), `4c5e862` rtk update-path version-guard (complements the `e58037c` install bridge already ported), `c76479f` design-motion sync, `e65796f` SC1091 lint. B soak journal (find-skills day1 / TTY #3 / rtk-update #4) folded here, not cherry-picked — divergent journal tails conflict (STOP-on-conflict honored, extract-consolidate fallback). C all covered/skip: `93e43c0` attribution + `ae8ad86` model already on develop; `188a9a7` docs → /doc backlog (README missing semgrep/scan-secrets/verify+secure/ctx7). Registry (LRN-098/101, EVAL-015, BLK-016) already backfilled in the review run. Gate: 23/23 release-only commits classified, 0 orphan functional, 0 missing registry; make test GREEN, review-guards 5/0. version.txt stays 4.0.0 (fork intentional, D — `eb93050`).
- [[LRN-117]]: the fork silently orphaned functional CODE on develop (not just memory); the review back-merge caught ~half. Detecting it needs a code-level drift check (advisory, backlogged) — registry-sequence gaps alone miss it.
+8
View File
@@ -132,6 +132,7 @@ rules:
| LRN-114 | 2026-07-08 | editing a hook GENERATOR (_gitflow_emit_pre_commit) does NOT update the INSTALLED hook (.githooks/pre-commit) — silent drift; T10 diffs the allow/block verdict not content, T16 emits fresh in a throwaway repo → job7 gitleaks backstop inert on the repo 8 days | after editing a template-generated artifact: reinstall (install-hook) + a gate that diffs installed==emit | | LRN-114 | 2026-07-08 | editing a hook GENERATOR (_gitflow_emit_pre_commit) does NOT update the INSTALLED hook (.githooks/pre-commit) — silent drift; T10 diffs the allow/block verdict not content, T16 emits fresh in a throwaway repo → job7 gitleaks backstop inert on the repo 8 days | after editing a template-generated artifact: reinstall (install-hook) + a gate that diffs installed==emit |
| LRN-115 | 2026-07-08 | analyzer Edit/Write grants (seo/geo/validator) are NOT dead: needed to write the REPORT (VALIDATE/SEO/GEO.md); the "never edit" rule targets CODE, instruction-level (same as the patron) — verified false-positive | do NOT re-flag as a tool-grant defect; a report-only agent keeps Write for its own report | | LRN-115 | 2026-07-08 | analyzer Edit/Write grants (seo/geo/validator) are NOT dead: needed to write the REPORT (VALIDATE/SEO/GEO.md); the "never edit" rule targets CODE, instruction-level (same as the patron) — verified false-positive | do NOT re-flag as a tool-grant defect; a report-only agent keeps Write for its own report |
| LRN-116 | 2026-07-08 | memory backfill release→develop: a BLK marked "resolved" can have its RESOLUTION (code) missing from develop — BLK-016 resolved on release but rtk fix e58037c never back-merged → bug LIVE on develop | before backfilling a resolved blocker: verify the fix CODE is on the target branch, not just the registry entry | | LRN-116 | 2026-07-08 | memory backfill release→develop: a BLK marked "resolved" can have its RESOLUTION (code) missing from develop — BLK-016 resolved on release but rtk fix e58037c never back-merged → bug LIVE on develop | before backfilling a resolved blocker: verify the fix CODE is on the target branch, not just the registry entry |
| LRN-117 | 2026-07-08 | a release/develop fork silently orphans FUNCTIONAL code on develop, not just memory — RC soak fixes (find-skills, make-update TTY, rtk version-guard) lived only on release for the fork's duration; the review's memory back-merge caught only ~half | at release-finish/reconcile: list develop..release commits touching non-registry code (excl. merges/version) for back-merge review — a registry-gap check alone misses code |
--- ---
@@ -1180,3 +1181,10 @@ rules:
- **fix**: ported e58037c to develop (13-line idempotent bridge), THEN backfilled BLK-016 resolved. General: before backmerging a resolved blocker, grep the target for the fix's code signature. - **fix**: ported e58037c to develop (13-line idempotent bridge), THEN backfilled BLK-016 resolved. General: before backmerging a resolved blocker, grep the target for the fix's code signature.
- **future application**: gitflow divergence review — enumerate release-only COMMITS that touch code, not just memory; a feature can be parallel-merged while its RC-branch fix is orphaned. - **future application**: gitflow divergence review — enumerate release-only COMMITS that touch code, not just memory; a feature can be parallel-merged while its RC-branch fix is orphaned.
- **cousin**: [[LRN-036]] (PATH profile drift), [[LRN-047]] (silent degradation). - **cousin**: [[LRN-036]] (PATH profile drift), [[LRN-047]] (silent degradation).
## LRN-117 — A release/develop fork silently orphans functional CODE on develop, not just memory
- **pattern**: cutting release/1.0.0 and continuing on develop, the RC-branch bug fixes (find-skills drop `095d881`, make-update TTY guard `a1093ca`, rtk update-path version-guard `4c5e862`, rtk install bridge `e58037c`, SC1091 lint `e65796f`) landed ONLY on release. They were live-broken on develop for the whole fork duration (rtk compression dead, `make update` dies non-interactively). The review's memory back-merge caught the registry gaps and one code fix (rtk bridge); a full back-merge found ~5 more functional commits.
- **why it hides**: registry-sequence gaps (missing LRN/BLK/EVAL ids) are easy to detect; orphaned CODE has no sequence to check. A feature can be parallel-merged to both branches while an RC-branch fix commit is never back-merged, and nothing flags it.
- **fix**: at release-finish / in /reconcile, list `develop..release/*` commits touching functional files (exclude merges, `.claude/**`, version.txt/CHANGELOG) and present them for back-merge review. Advisory, NOT a hard make-test gate — cherry-picks land with new SHAs so the source commit stays in the range; automatic "already-ported?" equivalence is unreliable and would false-positive. Backlogged.
- **future application**: any long-lived fork (release/*, long feature) — audit CODE divergence, not just declared/registry state ([[LRN-034]] narrated ≠ ground truth, applied to branches).
- **cousin**: [[LRN-116]] (a resolved blocker's fix can be missing from develop), [[BDR-054]] (supersession-trace discipline).
+26
View File
@@ -1,5 +1,31 @@
# TODO # TODO
## 2026-07-08 — full back-merge release/1.0.0→develop (chore/backmerge-release-full)
Genèse : la revue avait porté ~5/19 commits ; back-merge complet demandé. Cherry-pick par
catégorie, 1 commit atomique/item, make test après chaque code. Branche non mergée (gate humain).
- [x] A CODE (5 cherry-picks, make test GREEN chacun) : 095d881 drop find-skills (5a1fff5),
a1093ca TTY-guard make-update (ce07e55, prouvé EOF exit1→exit0), 4c5e862 rtk version-guard
(3049250, complète le pont e58037c déjà porté — fichiers/concerns distincts), c76479f
design-motion sync (82ce02c), e65796f SC1091 lint (fcdb157, shellcheck 0 SC1091).
- [x] B JOURNAL : cherry-pick direct conflicte (tails journal divergents) → STOP honoré,
fallback note consolidée sous journal 2026-07-08. TODO /deploy ca9fa8f skip (release-specific).
- [x] C DÉCISION/DOUBLON tous skip vérifiés : 93e43c0 attribution + ae8ad86 model (opus[1m]=Opus4.8)
déjà sur develop ; a623514/74d3804/2b4e740 registres déjà backfillés (run revue) ;
188a9a7 docs → backlog /doc ci-dessous.
- [x] D fork version 1eb5b08/eb93050 intouchés — version.txt reste 4.0.0.
- [x] GATE FINAL : 23/23 commits release-only classifiés, 0 code orphelin, 0 entrée registre
manquante ; make test GREEN + review-guards 5/0. Capitalize [[LRN-117]] structurel.
### Backlog (issu du back-merge)
- [ ] **/doc** — README develop ne documente pas semgrep / scan-secrets / verify+secure pipeline /
ctx7 (delta de 188a9a7, non porté car base README divergente job3 + CHANGELOG version-entangled).
Une passe /doc doit combler ces sujets sur le README réécrit de develop.
- [ ] **release-drift advisory** ([[LRN-117]]) — check qui liste les commits `develop..release/*`
touchant du CODE fonctionnel (exclut merges, `.claude/**`, version.txt/CHANGELOG) pour revue
de back-merge. Advisory, PAS un gate make-test dur : les cherry-picks landent avec de nouveaux
SHA → le commit source reste dans le range → équivalence "déjà porté ?" non fiable automatiquement
(faux positifs). Cible : étape release-finish ou /reconcile, pas run-review-guards.
## 2026-07-08 — review remediation (chore/review-remediation) ## 2026-07-08 — review remediation (chore/review-remediation)
Genèse : `.audit/review-release-1.0.0.md` (revue adversariale des 9 jobs). GO user, Genèse : `.audit/review-release-1.0.0.md` (revue adversariale des 9 jobs). GO user,
ordre imposé. Déviation justifiée : 1 branche (pas 1/EP) car le gate fil-rouge (step 6) ordre imposé. Déviation justifiée : 1 branche (pas 1/EP) car le gate fil-rouge (step 6)
-1
View File
@@ -68,7 +68,6 @@ skills/impeccable
# External skills installed via `npx skills add` — auto-created by link.sh # External skills installed via `npx skills add` — auto-created by link.sh
skills/darwin-skill skills/darwin-skill
skills/find-skills
# Context7 docs-lookup skill — installed by `ctx7 setup --claude --cli` # Context7 docs-lookup skill — installed by `ctx7 setup --claude --cli`
# (install-plugins.sh Step 6, when absent) into ~/.claude/skills (a symlink to # (install-plugins.sh Step 6, when absent) into ~/.claude/skills (a symlink to
+3
View File
@@ -25,6 +25,9 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
- `gitflow_finish` ignored its `<type> <name>` arguments and always merged the checked-out branch — naming a different branch silently merged the wrong one. The arguments are now an optional safety assertion: if given and not equal to the current branch, `finish` refuses with a clear error instead of merging. No-argument calls (the only real caller) are unchanged. - `gitflow_finish` ignored its `<type> <name>` arguments and always merged the checked-out branch — naming a different branch silently merged the wrong one. The arguments are now an optional safety assertion: if given and not equal to the current branch, `finish` refuses with a clear error instead of merging. No-argument calls (the only real caller) are unchanged.
- `doctor.sh` false-warnings removed (a check that cries wolf is one you learn to ignore): `cargo` absence no longer claims "RTK unavailable" (RTK ships as a prebuilt binary); `check_symlink` no longer flags files reached through directory-level symlinks (e.g. `hooks/session-start.sh`); the GStack check counts the per-skill symlinks instead of a `skills/gstack` link that `link.sh` deliberately removes; the token-budget estimate is measured against the ~200k context window instead of a mis-framed "~11k session budget" that produced a false "92% CRITICAL". - `doctor.sh` false-warnings removed (a check that cries wolf is one you learn to ignore): `cargo` absence no longer claims "RTK unavailable" (RTK ships as a prebuilt binary); `check_symlink` no longer flags files reached through directory-level symlinks (e.g. `hooks/session-start.sh`); the GStack check counts the per-skill symlinks instead of a `skills/gstack` link that `link.sh` deliberately removes; the token-budget estimate is measured against the ~200k context window instead of a mis-framed "~11k session budget" that produced a false "92% CRITICAL".
### Removed
- **find-skills** (alchaincyf) — skill-discovery helper dropped from the toolchain (install/update/link/toggle/advisor). Never used, and its `make update` refresh step had started failing on clone timeouts. The discovery use case stays reachable manually: `npx -y skills find <query>`.
## [4.0.0] — 2026-06-30 ## [4.0.0] — 2026-06-30
### Added ### Added
+3 -3
View File
@@ -19,7 +19,7 @@ Detect active plugins and project signals. Recommend enable/disable. Apply compa
claude plugin list 2>/dev/null || echo "plugin-list-unavailable" claude plugin list 2>/dev/null || echo "plugin-list-unavailable"
# External (non-marketplace) tools status — gstack, emil-design-eng, # External (non-marketplace) tools status — gstack, emil-design-eng,
# darwin-skill, find-skills. Managed by lib/toggle-external.sh since # darwin-skill. Managed by lib/toggle-external.sh since
# `claude plugin enable|disable` does not apply to them. # `claude plugin enable|disable` does not apply to them.
bash "$HOME/.claude/lib/toggle-external.sh" list 2>/dev/null || echo "toggle-external-unavailable" bash "$HOME/.claude/lib/toggle-external.sh" list 2>/dev/null || echo "toggle-external-unavailable"
@@ -353,8 +353,8 @@ RULE: IF `complex-arch` signal (multiple services, event bus, distributed system
## TOGGLING EXTERNAL TOOLS ## TOGGLING EXTERNAL TOOLS
Marketplace plugins toggle via `claude plugin enable|disable <name>@<marketplace>`. Marketplace plugins toggle via `claude plugin enable|disable <name>@<marketplace>`.
Non-marketplace tools (gstack per-skill symlinks, emil-design-eng, darwin-skill, Non-marketplace tools (gstack per-skill symlinks, emil-design-eng, darwin-skill)
find-skills) toggle via `bash $HOME/.claude/lib/toggle-external.sh enable|disable <tool>`. toggle via `bash $HOME/.claude/lib/toggle-external.sh enable|disable <tool>`.
When a recommendation flips the state of one of those tools, emit the exact When a recommendation flips the state of one of those tools, emit the exact
command — never write files directly. command — never write files directly.
+1 -1
View File
@@ -36,7 +36,7 @@ fi
# ── Load shared detection library ── # ── Load shared detection library ──
_lib="$(dirname "${BASH_SOURCE[0]}")/../lib/detect-plugins.sh" _lib="$(dirname "${BASH_SOURCE[0]}")/../lib/detect-plugins.sh"
if [ -f "$_lib" ]; then if [ -f "$_lib" ]; then
# shellcheck source=../lib/detect-plugins.sh # shellcheck source=../lib/detect-plugins.sh disable=SC1091
source "$_lib" source "$_lib"
else else
echo "⚠️ lib/detect-plugins.sh not found — config broken, run: bash link.sh" echo "⚠️ lib/detect-plugins.sh not found — config broken, run: bash link.sh"
+2 -3
View File
@@ -197,6 +197,7 @@ if command -v cargo &>/dev/null; then
else else
info "Installing Rust (rustup)..." info "Installing Rust (rustup)..."
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path
# shellcheck source=/dev/null
source "$HOME/.cargo/env" source "$HOME/.cargo/env"
ok "Rust installed: $(cargo --version)" ok "Rust installed: $(cargo --version)"
fi fi
@@ -832,7 +833,6 @@ echo ""
NPX_SKILLS=( NPX_SKILLS=(
"alchaincyf/darwin-skill" "alchaincyf/darwin-skill"
"alchaincyf/find-skills"
) )
# `skills add` resolves its target (.agents/skills/, skills-lock.json) RELATIVE # `skills add` resolves its target (.agents/skills/, skills-lock.json) RELATIVE
@@ -985,7 +985,7 @@ echo ""
# STEP 10 — REFRESH SYMLINKS (final, so this script is self-sufficient) # STEP 10 — REFRESH SYMLINKS (final, so this script is self-sufficient)
# ============================================================ # ============================================================
# Steps 2/8/8.5 INSTALL skills (gstack submodule, emil/frontend/motion, npx # Steps 2/8/8.5 INSTALL skills (gstack submodule, emil/frontend/motion, npx
# darwin/find-skills) that link.sh must symlink into ~/.claude/skills/. Since # darwin-skill) that link.sh must symlink into ~/.claude/skills/. Since
# link.sh runs BEFORE this script in install.sh, those symlinks would be missing # link.sh runs BEFORE this script in install.sh, those symlinks would be missing
# on a fresh run until link.sh is run again by hand. Re-run it here so # on a fresh run until link.sh is run again by hand. Re-run it here so
# `make plugin` (and `make install`) finish complete — nothing left to do. # `make plugin` (and `make install`) finish complete — nothing left to do.
@@ -1023,7 +1023,6 @@ echo " 🔄 frontend-design — distinctive frontend interfaces, anti-AI-
echo " 🔄 impeccable — /impeccable design verbs + 45-rule deterministic detector (npx impeccable detect)" echo " 🔄 impeccable — /impeccable design verbs + 45-rule deterministic detector (npx impeccable detect)"
echo " 🔄 design-motion-principles — motion/animation design, 3-designer lens (kylezantos)" echo " 🔄 design-motion-principles — motion/animation design, 3-designer lens (kylezantos)"
echo " 🔄 darwin-skill — autonomous skill optimizer (npx skills, ~/.agents/skills/)" echo " 🔄 darwin-skill — autonomous skill optimizer (npx skills, ~/.agents/skills/)"
echo " 🔄 find-skills — skill discovery helper (npx skills, ~/.agents/skills/)"
echo " 🔄 magic MCP — 21st-dev UI generation MCP (toggle: lib/toggle-external.sh enable magic)" echo " 🔄 magic MCP — 21st-dev UI generation MCP (toggle: lib/toggle-external.sh enable magic)"
echo "" echo ""
echo " All plugins installed at: user scope (~/.claude/plugins/)" echo " All plugins installed at: user scope (~/.claude/plugins/)"
+5 -6
View File
@@ -20,7 +20,6 @@
# gstack — per-skill symlinks populated by gstack's own setup # gstack — per-skill symlinks populated by gstack's own setup
# emil-design-eng — single symlink → skills-external/emil-design-eng # emil-design-eng — single symlink → skills-external/emil-design-eng
# darwin-skill — single symlink → ~/.agents/skills/darwin-skill # darwin-skill — single symlink → ~/.agents/skills/darwin-skill
# find-skills — single symlink → ~/.agents/skills/find-skills
# magic — 21st-dev Magic MCP server (API key in .env) # magic — 21st-dev Magic MCP server (API key in .env)
# #
# For fine-grained activation (only design skills, only qa skills, only # For fine-grained activation (only design skills, only qa skills, only
@@ -41,7 +40,7 @@ warn() { echo -e "${YELLOW}⚠${NC} $1"; }
err() { echo -e "${RED}✗${NC} $1"; } err() { echo -e "${RED}✗${NC} $1"; }
# All non-plugin tools this script can toggle. # All non-plugin tools this script can toggle.
MANAGED_TOOLS=(gstack emil-design-eng darwin-skill find-skills magic) MANAGED_TOOLS=(gstack emil-design-eng darwin-skill magic)
# Load MAGIC_API_KEY (and any other secrets) from $REPO/.env if present. # Load MAGIC_API_KEY (and any other secrets) from $REPO/.env if present.
# Called only by the magic branch — other tools don't need env vars. # Called only by the magic branch — other tools don't need env vars.
@@ -81,7 +80,7 @@ status_tool() {
[ -d "$REPO/skills-external/emil-design-eng" ] || { echo "missing"; return; } [ -d "$REPO/skills-external/emil-design-eng" ] || { echo "missing"; return; }
[ -e "$SKILLS_DIR/emil-design-eng" ] && echo "enabled" || echo "disabled" [ -e "$SKILLS_DIR/emil-design-eng" ] && echo "enabled" || echo "disabled"
;; ;;
darwin-skill|find-skills) darwin-skill)
[ -d "$HOME/.agents/skills/$tool" ] || { echo "missing"; return; } [ -d "$HOME/.agents/skills/$tool" ] || { echo "missing"; return; }
[ -e "$SKILLS_DIR/$tool" ] && echo "enabled" || echo "disabled" [ -e "$SKILLS_DIR/$tool" ] && echo "enabled" || echo "disabled"
;; ;;
@@ -116,7 +115,7 @@ disable_tool() {
done < <(gstack_skills) done < <(gstack_skills)
ok "gstack disabled ($moved symlinks moved)" ok "gstack disabled ($moved symlinks moved)"
;; ;;
emil-design-eng|darwin-skill|find-skills) emil-design-eng|darwin-skill)
if [ -e "$SKILLS_DIR/$tool" ]; then if [ -e "$SKILLS_DIR/$tool" ]; then
rm -rf "${DISABLED_DIR:?}/${tool:?}" rm -rf "${DISABLED_DIR:?}/${tool:?}"
mv "$SKILLS_DIR/$tool" "$DISABLED_DIR/$tool" mv "$SKILLS_DIR/$tool" "$DISABLED_DIR/$tool"
@@ -158,11 +157,11 @@ enable_tool() {
ok "gstack enabled ($moved symlinks restored)" ok "gstack enabled ($moved symlinks restored)"
fi fi
;; ;;
emil-design-eng|darwin-skill|find-skills) emil-design-eng|darwin-skill)
local src local src
case "$tool" in case "$tool" in
emil-design-eng) src="$REPO/skills-external/$tool" ;; emil-design-eng) src="$REPO/skills-external/$tool" ;;
darwin-skill|find-skills) src="$HOME/.agents/skills/$tool" ;; darwin-skill) src="$HOME/.agents/skills/$tool" ;;
esac esac
if [ -e "$DISABLED_DIR/$tool" ]; then if [ -e "$DISABLED_DIR/$tool" ]; then
rm -rf "${SKILLS_DIR:?}/${tool:?}" rm -rf "${SKILLS_DIR:?}/${tool:?}"
+1 -1
View File
@@ -90,7 +90,7 @@ done
# absolute paths so the link stays valid regardless of where the # absolute paths so the link stays valid regardless of where the
# repo is cloned (relative ../../ paths broke on repos deeper than # repo is cloned (relative ../../ paths broke on repos deeper than
# one level below $HOME). # one level below $HOME).
NPX_EXTERNAL_SKILLS=(darwin-skill find-skills) NPX_EXTERNAL_SKILLS=(darwin-skill)
for _ext in "${NPX_EXTERNAL_SKILLS[@]}"; do for _ext in "${NPX_EXTERNAL_SKILLS[@]}"; do
_target="$HOME/.agents/skills/$_ext" _target="$HOME/.agents/skills/$_ext"
_link="$REPO/skills/$_ext" _link="$REPO/skills/$_ext"
@@ -1,63 +1,77 @@
<!-- <!--
Demo Shell — design-motion-principles v2.1 Demo Shell — design-motion-principles
=========================================== =====================================
This file is a template the audit agent reads during STEP 3 of the Minimal, isolated reference for a single demo card. The agent reads this
audit workflow (see ../SKILL.md). The agent embeds one .demo-card per during STEP 3 of the audit workflow (see ../SKILL.md) and uses it as the
Critical or Important finding (per R4 in the plan — Opportunities do per-finding template — one .demo block per Critical or Important finding
not get demo cards). (Opportunities don't get demo cards).
How to use this file as the agent The full worked example with five demo cards in context lives in
--------------------------------- references/report-template.html. This file is intentionally minimal:
one card, every contract visible, no report scaffolding.
How the agent uses this file
----------------------------
1. Copy the entire <style> block into the report's <head>. The shell's 1. Copy the entire <style> block into the report's <head>. The shell's
CSS variables, .demo-card layout, loop indicator, and the tokens, .demo layout, stage colors, segmented control, and the
prefers-reduced-motion guard are shared across all demo cards in prefers-reduced-motion guard are SHARED across every demo card in the
the report — they are not duplicated per finding. report and must not be duplicated per finding.
2. For each finding {n} (1-indexed across the whole report): 2. For each finding {n} (1-indexed across the whole report):
a. Replace the MOTION-CODE-SLOT-{n} comment in <style> with the a. Replace the MOTION-CODE-SLOT-{n} comment with the per-finding
per-finding @keyframes block AND any .demo-card-{n}__motion-target @keyframes m{n} block AND the .demo-{n}__mt selector rule.
selector rules. Use the suffix {n} so multiple findings in one Suffix {n} so multiple findings cannot collide on keyframe
report do not collide on keyframe names or target selectors. names or selectors.
b. Replace the DEMO-CARD-MOTION-SLOT-{n} comment in the .demo-card b. Replace the DEMO-CARD-MOTION-SLOT-{n} comment in the .demo__stage
markup with the actual motion-target element. Its class must be markup with the actual motion-target element. Its class must be
.demo-card-{n}__motion-target so it matches the rules above. .demo-{n}__mt so it matches the rules above.
c. Set the .demo-card__header text to a short title for the c. Set .demo__title to a short title for the recommended motion
recommended motion (e.g., "Subtle enter: opacity + translateY + blur"). (e.g., "Quick tab crossfade", "Sheet enter (mirror for exit)").
d. Set the .demo-card__subhead text to the duration plus easing or d. Set .demo__timing to the duration plus easing (e.g.,
other relevant values (e.g., "300ms · ease-out"). The subhead "180ms · ease-out", "300ms · ease-out-quint"). The subhead
ALWAYS renders — the agent populates it for every demo so card ALWAYS renders — populate it for every demo so card heights
heights stay consistent across the report. stay consistent across the report.
e. Renumber the four radio ids from st1-* to st{n}-*, and the
labels' for= attributes to match.
3. Per-finding code MUST honor these contracts: 3. Per-finding code MUST honor these contracts:
- Do not redefine the shell's CSS variables (--bg, --fg, --border, - Do NOT redefine the shell's tokens (--ink, --paper, --surface-2,
--accent, --loop-dim, --card-radius, --card-padding, --gap, --line, --line-strong, --st-bg, --st-fg, --st-line, --st-dim,
--sans, --mono). Use them via var(). --sans, --mono). Use them via var().
- Do not modify the prefers-reduced-motion block. The shell's - Do NOT modify the prefers-reduced-motion block. The shell's
guard collapses all .demo-card-{n}__motion-target animations to guard collapses all .demo-{n}__mt animations to none. The
none. The per-finding @keyframes 100% state must match the per-finding @keyframes 100% state MUST match the motion-target
motion-target's default rendered state so the reduce-motion element's default static rendering so the reduce-motion fallback
fallback shows the correct final visual. shows the correct final visual.
- Per-finding @keyframes use the 0% / 66% / 100% cadence: - Per-finding @keyframes use the 0% / ~60% / 100% cadence:
0% = start state, 66% = motion complete (~2s in), 100% = hold 0% = start state, ~60% = motion complete (~1.8s in),
(~1s). The shell uses animation-duration: 3s. 100% = hold (~1.2s). The shell uses animation-duration: 3s.
4. Demo cards are non-interactive. They have no hover or focus state 4. Motion targets and ANY UI primitives inside the stage (.ui-btn,
beyond the default outline suppression. tabindex="-1" keeps them .ui-card, .ui-row, .ui-check, .ui-num, .ui-label, badge, etc.)
out of keyboard nav order — readers tab through findings, not use --st-bg / --st-fg / --st-line / --st-dim — NEVER --accent or
through demo cards. other page tokens. This guarantees the demo contrasts correctly
when its stage is locked to a different theme than the report.
5. Demo cards are non-interactive. The stage toggle radios are the
only interactive element. tabindex on the radios is fine; the
stage itself stays out of focus order.
Loop pacing Loop pacing
----------- -----------
animation-duration: 3s. Keyframes 0% / 66% / 100%. Motion 0-66% = ~2s, animation-duration: 3s. Keyframes 0% / ~60% / 100%. Motion 0–60% ≈ 1.8s,
hold 66-100% = ~1s, then the animation restarts. Per-finding code hold 60–100% ≈ 1.2s, then restart. Every demo in a report uses this
imitates this cadence so all demos in a report share the same rhythm. cadence so all cards share the same rhythm.
Empty state (this file rendered standalone) Standalone preview
------------------------------------------- ------------------
Opening this file directly in a browser shows one .demo-card with the Opening this file directly in a browser shows one .demo card with the
loop indicator and placeholder content. No motion plays — the agent loop indicator, the segmented Auto/Light/Dark stage toggle, and a
injects motion per finding when this template is embedded in a report. placeholder motion target (a simple shape that fades in). No real
per-finding motion plays — the agent injects motion per finding when
this template is embedded in a report. The shell also honors the
viewer's prefers-color-scheme for standalone rendering.
--> -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@@ -65,144 +79,197 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Demo Shell — design-motion-principles</title> <title>Demo Shell — design-motion-principles</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Familjen+Grotesk:wght@400;500;600;700&family=Public+Sans:wght@400;500;600;700&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style> <style>
:root { :root {
--bg: #ffffff; /* Cool slate-graphite neutrals (hue 255). In the full report these
--fg: #111111; are overridden by a global theme toggle; here the shell flips via
--border: #e5e5e5; prefers-color-scheme for standalone preview. */
--accent: #111111; --ink: oklch(0.155 0.006 255);
--loop-dim: rgba(0, 0, 0, 0.45); --surface: oklch(0.195 0.007 255);
--card-radius: 12px; --surface-2: oklch(0.235 0.008 255);
--card-padding: 20px; --paper: oklch(0.945 0.004 255);
--gap: 12px; --paper-dim: oklch(0.800 0.006 255);
--sans: system-ui, -apple-system, "Segoe UI", sans-serif; --muted: oklch(0.680 0.008 255);
--mono: ui-monospace, "SF Mono", Menlo, monospace; --faint: oklch(0.505 0.008 255);
--line: oklch(0.95 0.01 255 / 0.09);
--line-strong:oklch(0.95 0.01 255 / 0.16);
--display: "Familjen Grotesk", "Hanken Grotesk", system-ui, sans-serif;
--sans: "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
--mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: light) {
:root { :root {
--bg: #1a1a1a; --ink: oklch(0.985 0.003 255);
--fg: #f0f0f0; --surface: oklch(0.965 0.004 255);
--border: #333333; --surface-2: oklch(0.940 0.005 255);
--accent: #f0f0f0; --paper: oklch(0.180 0.006 255);
--loop-dim: rgba(240, 240, 240, 0.45); --paper-dim: oklch(0.330 0.008 255);
--muted: oklch(0.475 0.010 255);
--faint: oklch(0.640 0.009 255);
--line: oklch(0.180 0.010 255 / 0.12);
--line-strong:oklch(0.180 0.010 255 / 0.22);
} }
} }
* { box-sizing: border-box; }
body { body {
background: var(--bg); background: var(--ink);
color: var(--fg); color: var(--paper);
font-family: var(--sans); font-family: var(--sans);
margin: 0; margin: 0;
padding: 32px; padding: 48px;
}
.demo-card {
position: relative;
background: var(--bg);
color: var(--fg);
border: 1px solid var(--border);
border-radius: var(--card-radius);
padding: var(--card-padding);
max-width: 360px;
min-width: 280px;
font-family: var(--sans);
outline: none;
}
.demo-card:focus-visible {
outline: none;
}
.demo-card__header {
font-size: 0.875rem;
font-weight: 600;
color: var(--fg);
margin-bottom: 4px;
padding-right: 72px;
}
.demo-card__subhead {
font-family: var(--mono);
font-size: 0.75rem;
color: var(--loop-dim);
margin-bottom: var(--gap);
}
.demo-card__stage {
background: var(--bg);
border: 1px dashed var(--border);
border-radius: 8px;
padding: 24px;
min-height: 100px;
display: flex; display: flex;
align-items: center;
justify-content: center; justify-content: center;
overflow: hidden;
} }
.demo-card__loop-indicator { /* visually-hidden (stage-toggle radios) */
position: absolute; .vh { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
top: 12px;
right: 12px; /* ───────────── Demo card ───────────── */
font-family: var(--mono); .demo { position: relative; width: 380px; }
font-size: 0.7rem; .demo__bar { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
color: var(--loop-dim); .demo__meta { min-width: 0; }
letter-spacing: 0.02em; .demo__title { display: block; font-size: 13.5px; font-weight: 600; color: var(--paper); line-height: 1.3; }
user-select: none; .demo__timing { display: block; font-family: var(--mono); font-size: 11px; color: var(--muted); margin-top: 2px; font-variant-numeric: tabular-nums; }
.demo__controls { display: flex; align-items: center; gap: 12px; flex-shrink: 0; }
.demo__loop { font-family: var(--mono); font-size: 12px; color: var(--faint); }
/* segmented Auto / Light / Dark stage toggle (pure CSS).
Auto = follow ambient theme via --surface-2 + --paper.
Light / Dark = locked overrides (hardcoded values, ignore ambient). */
.seg { display: inline-flex; border: 1px solid var(--line-strong); border-radius: 100px; overflow: hidden; }
.seg label { font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); padding: 4px 9px; cursor: pointer; user-select: none; transition: background 0.15s ease, color 0.15s ease; }
.demo input[id$="-a"]:checked ~ .demo__bar .seg label[for$="-a"],
.demo input[id$="-l"]:checked ~ .demo__bar .seg label[for$="-l"],
.demo input[id$="-d"]:checked ~ .demo__bar .seg label[for$="-d"] {
background: color-mix(in oklch, var(--paper) 12%, transparent);
color: var(--paper);
} }
.demo__stage {
/* AUTO default — stage uses the ambient elevated surface */
--st-bg: var(--surface-2);
--st-fg: var(--paper);
--st-line: var(--line-strong);
--st-dim: color-mix(in oklch, var(--paper) 55%, transparent);
background: var(--st-bg); color: var(--st-fg);
border: 1px solid var(--line); border-radius: 10px;
padding: 32px 24px; min-height: 168px;
display: flex; align-items: center; justify-content: center; overflow: hidden;
}
/* Locked Light: hardcoded light, regardless of ambient theme */
.demo input[id$="-l"]:checked ~ .demo__stage {
--st-bg: oklch(0.985 0.003 255);
--st-fg: oklch(0.180 0.006 255);
--st-line: oklch(0.180 0.010 255 / 0.18);
--st-dim: oklch(0.180 0.010 255 / 0.45);
}
/* Locked Dark: hardcoded dark, regardless of ambient theme */
.demo input[id$="-d"]:checked ~ .demo__stage {
--st-bg: oklch(0.180 0.006 255);
--st-fg: oklch(0.985 0.003 255);
--st-line: oklch(0.985 0.010 255 / 0.18);
--st-dim: oklch(0.985 0.010 255 / 0.50);
}
/* Generic UI primitives for use inside motion targets. All theme via
stage tokens, NEVER page tokens — so they contrast correctly when a
stage is locked to a different theme than the report. */
.ui-btn { display: inline-flex; align-items: center; justify-content: center; padding: 9px 18px; border-radius: 8px; background: var(--st-fg); color: var(--st-bg); font-family: var(--sans); font-size: 13px; font-weight: 600; }
.ui-card { background: var(--st-bg); border: 1px solid var(--st-line); border-radius: 10px; padding: 14px 16px; min-width: 180px; box-shadow: 0 8px 24px oklch(0 0 0 / 0.12); }
.ui-row { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--st-fg); }
.ui-check { width: 22px; height: 22px; border-radius: 7px; background: var(--st-fg); display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
.ui-check svg { width: 13px; height: 13px; stroke: var(--st-bg); }
.ui-num { font-family: var(--display); font-size: 34px; font-weight: 700; color: var(--st-fg); font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
.ui-label { font-family: var(--mono); font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--st-dim); }
/* prefers-reduced-motion — disables every per-finding animation and
hides the loop indicator. The per-finding @keyframes 100% state
must match the motion-target's default rendering so the reduce
fallback shows the correct final visual. */
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.demo-card__loop-indicator { .demo__loop { display: none; }
display: none; [class*="__mt"], [class*="__mt"] * { animation: none !important; transition: none !important; }
}
[class*="__motion-target"] {
animation: none !important;
transition: none !important;
}
} }
/* MOTION-CODE-SLOT-{n} /* MOTION-CODE-SLOT-{n}
Per-finding @keyframes and .demo-card-{n}__motion-target rules Per-finding @keyframes and .demo-{n}__mt rules go here. {n} is the
go here. {n} = finding's 1-indexed position across the report. finding's 1-indexed position across the report.
Example shape (replace per finding): Example shape (replace per finding — wrap in
@media (prefers-reduced-motion: no-preference) { ... }):
@keyframes motion-1-enter { @media (prefers-reduced-motion: no-preference) {
0% { opacity: 0; transform: translateY(8px); filter: blur(4px); } @keyframes m1 {
66% { opacity: 1; transform: translateY(0); filter: blur(0); } 0% { opacity: 0; transform: translateY(8px); filter: blur(4px); }
100% { opacity: 1; transform: translateY(0); filter: blur(0); } 60% { opacity: 1; transform: translateY(0); filter: blur(0); }
} 100% { opacity: 1; transform: translateY(0); filter: blur(0); }
.demo-card-1__motion-target { }
animation: motion-1-enter 3s infinite; .demo-1__mt {
display: inline-block; animation: m1 3s cubic-bezier(0.22, 1, 0.36, 1) infinite;
padding: 8px 16px; }
border-radius: 6px;
background: var(--accent);
color: var(--bg);
font-family: var(--sans);
} }
*/ */
/* Placeholder motion for standalone preview only — agent strips this
when composing the report. Demonstrates the 0 / 60 / 100 cadence. */
@media (prefers-reduced-motion: no-preference) {
@keyframes m-placeholder {
0% { opacity: 0; transform: translateY(8px); filter: blur(4px); }
60% { opacity: 1; transform: translateY(0); filter: blur(0); }
100% { opacity: 1; transform: translateY(0); filter: blur(0); }
}
.demo-1__mt {
animation: m-placeholder 3s cubic-bezier(0.22, 1, 0.36, 1) infinite;
}
}
</style> </style>
</head> </head>
<body> <body>
<article class="demo-card" tabindex="-1"> <div class="demo">
<div class="demo-card__loop-indicator">↻ looping</div> <input class="vh" type="radio" name="st1" id="st1-a" checked>
<div class="demo-card__header">Recommended motion title</div> <input class="vh" type="radio" name="st1" id="st1-l">
<div class="demo-card__subhead">300ms · ease-out</div> <input class="vh" type="radio" name="st1" id="st1-d">
<div class="demo-card__stage"> <div class="demo__bar">
<div class="demo__meta">
<span class="demo__title">Recommended motion title</span>
<span class="demo__timing">300ms · ease-out</span>
</div>
<div class="demo__controls">
<div class="seg">
<label for="st1-a">Auto</label>
<label for="st1-l">Light</label>
<label for="st1-d">Dark</label>
</div>
<span class="demo__loop">↻</span>
</div>
</div>
<div class="demo__stage">
<!-- DEMO-CARD-MOTION-SLOT-{n} <!-- DEMO-CARD-MOTION-SLOT-{n}
Per-finding motion-target element goes here. Its class must Per-finding motion-target element goes here. Its class must
match the rules in MOTION-CODE-SLOT-{n} above: match the rules in MOTION-CODE-SLOT-{n} above:
<div class="demo-card-{n}__motion-target">...</div> <div class="demo-{n}__mt">...content...</div>
The element's contents are agent-determined (a button shape, The element's contents are agent-determined (a button shape,
a card, an icon, a list of items for stagger demos, etc.). --> a card, an icon, a row of items for stagger demos, a number,
<span style="color: var(--loop-dim); font-family: var(--mono); font-size: 0.75rem;"> a badge, etc.). All inner UI primitives use the stage tokens
(motion preview renders here per finding) (--st-fg / --st-bg / --st-line / --st-dim) — NEVER page tokens. -->
</span> <div class="demo-1__mt">
<div class="ui-card">
<div class="ui-label" style="margin-bottom: 6px;">Placeholder</div>
<div class="ui-row">
<span class="ui-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3.5"><path d="M5 13l4 4L19 7"/></svg></span>
<span>(motion preview renders here per finding)</span>
</div>
</div>
</div>
</div> </div>
</article> </div>
</body> </body>
</html> </html>
@@ -1,19 +1,28 @@
# Output Format # Output Format
This file defines the audit's two output modes: The audit produces one of two outputs:
- **HTML mode (default)** — a self-contained `.html` file written to the audited project's `motion-audits/` directory and opened in the user's default browser. Each Critical or Important finding gets an auto-looping CSS demo card beside it. - **HTML mode (default)** — a self-contained `.html` file written to the audited project's `motion-audits/` directory and opened in the user's default browser. Each Critical or Important finding gets a live, looping CSS demo card beside it.
- **Terminal mode (flag-triggered)** — the decorated-markdown report rendered inline in the conversation. Use when the user passes `--terminal`, `--inline`, "show the full report inline," "skip the HTML," or any natural-language equivalent. No HTML file is written. - **Terminal mode (flag-triggered)** — a decorated-markdown report rendered inline in the conversation. Use when the user passes `--terminal`, `--inline`, `--no-html`, "show the full report inline," or any natural-language equivalent. No HTML file is written.
The two modes contain the same audit content; only the rendering differs. Do not summarize — users want full per-lens perspectives. Both modes carry the same audit content; only the rendering differs. Do not summarize — users want full per-lens perspectives.
--- ---
## HTML mode ## HTML mode
### Canonical references
| File | Role |
|---|---|
| `references/report-template.html` | **Source of truth.** Full worked example (fictional "Tally" habit tracker, React + Framer Motion). Every section, every token, every pattern. When in doubt about layout, structure, or styling, READ this file. |
| `references/demo-shell.html` | Minimal isolated example of a single demo card with the per-finding slot pattern. Used as a per-finding template snippet. |
The agent builds the report by reading these two files and adapting them to the audited project — same architecture, audit-specific content.
### File structure ### File structure
The HTML output is a single self-contained `.html` document with everything inlined — no external CSS, no external JS, no external fonts (fonts may degrade gracefully if a CDN reference is used). The file scaffolds: Single self-contained `.html`. All CSS inlined. No external JS. Fonts loaded via Google Fonts CDN (Familjen Grotesk / Public Sans / Geist Mono) with full system-stack fallbacks so the file degrades gracefully offline.
``` ```
<!DOCTYPE html> <!DOCTYPE html>
@@ -22,310 +31,233 @@ The HTML output is a single self-contained `.html` document with everything inli
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{project-name} motion audit — {ISO date}</title> <title>{project-name} motion audit — {ISO date}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Familjen+Grotesk:wght@400;500;600;700&family=Public+Sans:wght@400;500;600;700&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style> <style>
/* 1. Demo shell tokens, .demo-card layout, prefers-reduced-motion */ /* 1. :root token block (neutrals, accent aliases, severity, timing ramp, spacing, fonts)
/* — copied from references/demo-shell.html */ 2. :root:has(#theme-light:checked) light-mode token override
3. Layout + component CSS (header, lens-table, timing-figure, lens-sec, finding-row, demo, rec, ref-summary)
/* 2. Report layout tokens: hero, finding-row, perspective-section, 4. Per-finding @keyframes m{n} + .demo-{n}__mt rules, one block per Critical or Important finding,
severity-table, recommendation-summary */ {n} = 1-indexed across the whole report (collision-free) */
/* 3. Per-finding @keyframes and .demo-card-{n}__motion-target
rules, generated per audit — one block per Critical or
Important finding, suffixed by the finding's 1-indexed
position across the report */
</style> </style>
</head> </head>
<body> <body>
<!-- Hero --> <!-- Global theme switch (Dark/Light radios) -->
<!-- Header (eyebrow, title, lede, meta, stats) -->
<!-- Overall Assessment --> <!-- Overall Assessment -->
<!-- Per-lens sections (primary, secondary, selective) --> <!-- 01 · Lens summary table -->
<!-- Combined Recommendations tables --> <!-- 02 · Where the timings land (duration-budget diagram) -->
<!-- Lens Reference Summary --> <!-- 03–05 · Per-lens sections (Jakub, Emil, Jhey — ordered by weighting for the audited context) -->
<!-- 06 · Combined recommendations tables -->
<!-- 07 · Lens reference summary -->
<!-- footer -->
</body> </body>
</html> </html>
``` ```
### Report's own motion posture ### Design system
The report itself has **no** entrance, scroll, or mount animations. No staggered reveals, no fade-in-on-scroll, no motion-on-mount outside the demo cards. The demo cards are the only animated elements in the document — anything else would reproduce the AI-slop patterns the skill audits against. Neutral-default, dual-mode, severity-driven.
### Hero header - **Neutrals.** Cool slate-graphite at hue 255, very low chroma (0.003–0.010). `--ink` is the page background; `--paper` is the foreground text. In light mode the two swap values via the `:root:has(#theme-light:checked)` override — every other token derives from these two and flips automatically.
- **Severity (FIXED, never adaptive).** Red `oklch(0.655 0.185 25)` (critical) · Amber `oklch(0.805 0.125 78)` (important) · Green `oklch(0.745 0.135 152)` (opportunity). Light-mode counterparts deepen L for contrast on white; hues stay constant.
- **Timing-budget ramp (FIXED).** Same hues as severity; used in section 02 only. Instant + responsive = green, deliberate = amber, sluggish = red.
- **Accent (NEUTRAL by default).** `--accent`, `--accent-soft`, `--accent-tint` alias to `--paper`, `--paper-dim`, and a low-alpha paper tint. The report has no chromatic primary color — severity is the only color in the document. An individual audit MAY repoint these three to a sampled brand color, but ONLY if the brand has at least ~40° hue clearance from each of the severity hues and is verified not to fall in the AI-cliché zone (neon cyan, purple-to-blue gradients).
- **Fonts.** Display = Familjen Grotesk, body = Public Sans, mono = Geist Mono. The mono carries timing values (`240ms · ease-out`) and all small labels — never substitute a more generic mono for the timing values.
Top of the document. Project name + ISO date + severity counts row + primary lens label. ### Dual theme
```html Pure-CSS toggle. Two radios (`#theme-dark` default-checked, `#theme-light`) live inside `.theme-switch` at the top of `.wrap`. `:root:has(#theme-light:checked)` overrides every theme-dependent token. No JS. Selector compatibility: `:has()` is Baseline 2023, supported by all modern browsers.
<header class="report-hero">
<h1>{project-name} motion audit</h1> The global toggle's visual control is a segmented `Dark / Light` pill, top-right of the page, styled to match the per-demo stage segmented control.
<p class="report-hero__date">{ISO date}</p>
<p class="report-hero__counts"> ### The report's motion posture
<a href="#critical-findings">🔴 Critical: {N}</a> ·
<a href="#important-findings">🟡 Important: {N}</a> · **The report itself has no entrance, scroll, or mount animation.** No staggered reveals. No fade-in-on-scroll. No motion on mount outside the demo cards. The demo cards are the only animated elements in the document — anything else would reproduce the AI-slop patterns this skill audits against.
<a href="#opportunity-findings">🟢 Opportunities: {N}</a>
</p> The one allowed transition: `border-color 0.2s ease` on lens-table rows and finding-rows for hover feedback. That's it.
<p class="report-hero__primary">Primary: {Designer Name} — {Perspective Handle}</p>
</header> ### Sections (in render order)
#### Global theme switch
First element inside `.wrap`, right-aligned segmented `Dark / Light` pill.
#### Header
```
.eyebrow ("MOTION AUDIT · DESIGN-MOTION-PRINCIPLES")
h1.title ({project name} — {one-line audit framing})
p.lede ({1–2 sentence project description})
.meta-row (what it is · stack)
.stats (Findings · Critical · Important · Opportunities — each is an anchor link to its rec table)
``` ```
The severity counts pair each emoji with a text label (`Critical: N`, not just `🔴 N`) so the severity signal is readable under red-green color vision deficiency. Each count is an anchor link to the corresponding section in the body — this is the navigation affordance for long audits with many findings. Each severity count pairs the number with a text label so the signal is readable under red-green color vision deficiency. Each count is an anchor link (`#rec-crit`, `#rec-imp`, `#rec-opp`) to the corresponding recommendation table.
### Overall Assessment #### Overall Assessment
One short paragraph in larger display type. Does this feel polished? Too much? Too little? What's working, what's not? Wraps in `<section class="assessment">` with a `mono-label` "OVERALL" eyebrow.
One short paragraph in larger type. Does this feel polished? Too much? Too little? What's working, what's not? #### 01 · Lens summary
3-row table, one row per practitioner. Columns: Lens (with name and weight chip) · Verdict (`Strong` / `Concern` / `Problem` / `Mixed` with a colored dot) · One-line read. Weight chips indicate `Primary` / `Secondary` / `Selective` per audit context.
```html #### 02 · Where the timings land — duration-budget diagram
<section class="report-assessment"> Motion-native analog of thumb-first's thumb-zone diagram. A horizontal SVG (`viewBox="0 0 660 300"`) plots Tally's animations as numbered dots on a 0–600ms scale with four zone bands:
<p>{one-paragraph assessment}</p>
</section> | Zone | Range | Color |
|---|---|---|
| Instant | 0–100ms | green (`--t-good`) |
| Responsive | 100–300ms | green (`--t-good`) |
| Deliberate | 300–500ms | amber (`--t-mid`) |
| Sluggish | 500ms+ | red (`--t-slow`) |
Animations with NO transition are plotted as hollow dashed circles at `x=40` (= 0ms). The paired key list to the right carries the action names and durations. A "What's off" block below explains the misalignments.
The SVG uses CSS-class-driven fills (via an inline `<style>` block) so the diagram re-tones with the global theme. Dot label color flips per theme (dark text on lighter dots in dark mode, light text on deeper dots in light mode).
#### 03–05 · Per-lens sections
Three sections, ordered by weighting (primary first). Each section:
```
.lens-sec__head (h3 "Designer — Perspective" + .lens-sec__weight chip)
p.lens-sec__verdict (verdict dot)
.lens-block "What's working well" (ul.lens-list.good with ✓ markers + file refs)
.lens-block "Issues to address" (one .finding-row per Critical/Important finding)
.lens-block "Opportunities" (ul.lens-list.opp with 💡 markers + file refs)
.lens-take ("Through {Designer}'s lens: {1–2 sentence summary}")
``` ```
### Per-lens sections Section heading: `Designer Name — Perspective Handle` (em-dash). Lens take is the documented lens summary, NOT a quote from the person — render as `Through {Designer}'s lens` (apostrophe-s).
Three sections in weighting order: primary, secondary, selective. Each section header pairs the designer name with the perspective handle using an em-dash (`Designer Name — Perspective Handle`): Three perspective handles:
```html | Designer | Handle |
<section class="perspective-section" id="perspective-emil">
<h2>Emil Kowalski — Restraint &amp; Speed</h2>
<div class="perspective-section__working-well">
<h3>What's Working Well</h3>
<ul>
<li>✓ {observation} — <code>{file.tsx:line}</code></li>
</ul>
</div>
<div class="perspective-section__issues" id="emil-issues">
<h3>Issues to Address</h3>
<!-- One .finding-row per Critical or Important finding under this lens -->
</div>
<div class="perspective-section__opportunities">
<h3>Opportunities</h3>
<ul>
<li>💡 {idea} — <code>{file.tsx:line}</code></li>
</ul>
</div>
<p class="perspective-section__quote"><strong>Through Emil's lens:</strong> {1-2 sentence summary}</p>
</section>
```
The three perspective handles:
| Designer | Perspective handle |
|---|---| |---|---|
| Emil Kowalski | Restraint & Speed | | Emil Kowalski | Restraint & Speed |
| Jakub Krehel | Production Polish | | Jakub Krehel | Production Polish |
| Jhey Tompkins | Experimentation & Delight | | Jhey Tompkins | Experimentation & Delight |
Always render section headers as `Designer Name — Perspective` (em-dash). Always close each section with the `Through {Designer}'s lens:` summary — it's a documented lens, not a quote from the person. #### Finding rows (Critical + Important only)
Each Critical or Important finding renders as a `.finding-row` inside its lens's "Issues to address" block:
### Finding rows (Critical + Important only) ```
.finding-row[data-sev="crit|imp"]
.finding-row__prose
.find-tags (severity chip + 1–2 lens chips)
h4.find-title
.find-body
<p><span class="label">What</span>{prose}</p>
<p><span class="label">Why it matters</span>{prose}</p>
<div class="fix"><p><span class="label">Recommended motion</span>{prose}</p></div>
<p class="find-loc"><code>{file:line}</code></p>
.demo
{radios + bar + stage with motion-target}
```
Each Critical or Important finding inside an `Issues to Address` block renders as a `.finding-row` with the issue prose on the left and the demo card on the right (two-column at desktop, stacked at narrow widths): Two-column at desktop (1fr 380px), stacks at narrow widths (≤860px).
Opportunities never render a `.finding-row` and never get a demo card. They appear in the per-lens `.lens-block "Opportunities"` as a `.lens-list.opp` bulleted list.
#### 06 · Combined recommendations
Three severity-grouped tables, in order: `Critical · must fix` (`#rec-crit`) → `Important · should fix` (`#rec-imp`) → `Opportunities · could enhance` (`#rec-opp`). Each has a `.tier-label` with severity-colored mono label, a horizontal rule, and a count. Columns: Issue · File · Fix (or Enhancement · Where · Impact for opportunities).
#### 07 · Lens Reference Summary
Closing `.ref-summary` block. Which lens was referenced most + why + how to lean differently (one line per lens). No new findings here.
#### Footer
Mono micro-row: project name + finding counts.
### Demo cards (the centerpiece)
Each Critical or Important finding gets one demo card. The card is the ONLY animated element in the report.
#### Markup pattern
```html ```html
<div class="finding-row" id="finding-{n}"> <div class="demo">
<div class="finding-row__prose"> <input class="vh" type="radio" name="st{n}" id="st{n}-a" checked>
<p class="finding-row__severity">🔴 Critical</p> <input class="vh" type="radio" name="st{n}" id="st{n}-l">
<h4>{finding title}</h4> <input class="vh" type="radio" name="st{n}" id="st{n}-d">
<p>{finding explanation}</p> <div class="demo__bar">
<p class="finding-row__location"><code>{file.tsx:line}</code></p> <div class="demo__meta">
</div> <span class="demo__title">{recommended motion title}</span>
<article class="demo-card" tabindex="-1"> <span class="demo__timing">{duration} · {easing}</span>
<div class="demo-card__loop-indicator">↻ looping</div>
<div class="demo-card__header">{recommended motion title}</div>
<div class="demo-card__subhead">{duration} · {easing}</div>
<div class="demo-card__stage">
<div class="demo-card-{n}__motion-target">{motion target markup}</div>
</div> </div>
</article> <div class="demo__controls">
<div class="seg">
<label for="st{n}-a">Auto</label>
<label for="st{n}-l">Light</label>
<label for="st{n}-d">Dark</label>
</div>
<span class="demo__loop">↻</span>
</div>
</div>
<div class="demo__stage">
<div class="demo-{n}__mt">{motion target markup}</div>
</div>
</div> </div>
``` ```
`{n}` is the finding's 1-indexed position **across the whole report** (not per-section). This guarantees `@keyframes motion-{n}-...` and `.demo-card-{n}__motion-target` selector names are unique across the document, so concatenating multiple findings' CSS in one `<style>` block does not produce keyframe-name collisions. `{n}` is the finding's 1-indexed position across the whole report (not per-section). This guarantees `@keyframes m{n}` and `.demo-{n}__mt` selectors are unique across the concatenated `<style>` block.
Opportunities never render a demo card. They appear in the per-lens section's `Opportunities` block as a plain bulleted list. #### Stage 3-state toggle
### Demo-shell embedding pattern | State | Behavior |
|---|---|
| **Auto** (default) | Stage colors follow the global theme via `--surface-2` + `--paper` tokens. Matches the rest of the report. |
| **Light** | Hardcoded light stage (`--st-bg: oklch(0.985 0.003 255)`, `--st-fg: oklch(0.180 0.006 255)`). Overrides global. |
| **Dark** | Hardcoded dark stage (inverted). Overrides global. |
The agent reads `references/demo-shell.html` and uses it as a template. For each Critical or Important finding: Pure CSS. Radio inputs precede `.demo__bar` and `.demo__stage` so sibling-combinator selectors (`#stN-l:checked ~ .demo__stage`) work.
1. **Generate the per-finding motion code.** Read the audited code, the relevant lens reference (`emil-kowalski.md`, `jakub-krehel.md`, `jhey-tompkins.md` — matching the lens this finding lives under), and `references/motion-cookbook.md` for the concrete recipe (easing, spring config, enter/exit shape). Write a CSS keyframe block + selector rules that demonstrate the recommended motion. Use the 0% / 66% / 100% cadence with `animation-duration: 3s` (~2s motion, ~1s hold, then loop). #### Stage tokens (used by motion targets)
2. **Inject the per-finding code into the report's `<style>` block.** Append a `@keyframes motion-{n}-...` block and a `.demo-card-{n}__motion-target { animation: ...; }` rule. The shell's CSS variables (`--bg`, `--fg`, `--border`, `--accent`, `--loop-dim`, `--sans`, `--mono`) are available — use them via `var()`. Do not redefine them. | Token | Role |
|---|---|
| `--st-bg` | Stage background color |
| `--st-fg` | Foreground / text / "ink" color on the stage |
| `--st-line` | Border / divider color on the stage |
| `--st-dim` | Dimmed text color on the stage |
3. **Inject the motion-target element into the `.demo-card__stage`.** The element's class must be `.demo-card-{n}__motion-target` so it matches the rules from step 2. Motion-target elements (`.ui-btn`, `.ui-card`, `.ui-row`, `.ui-check`, `.ui-num`, `.ui-label`, milestone badge, etc.) use `--st-fg` / `--st-bg` instead of `--accent` / page colors. This guarantees correct contrast even when a stage is locked to a different theme than the page.
4. **Set the demo card's header and subhead.** Header = short title for the recommended motion (e.g., "Subtle enter: opacity + translateY + blur"). Subhead = duration + easing in monospace (e.g., "300ms · ease-out"). The subhead always renders — populate it for every demo. #### Per-finding motion code
5. **Honor the prefers-reduced-motion guard.** The shell's `@media (prefers-reduced-motion: reduce)` block disables all `[class*="__motion-target"]` animations. The per-finding `@keyframes` 100% values MUST match the motion-target element's default static rendering so the reduce-motion fallback shows the correct final visual. Do not write per-finding overrides inside the reduce-motion block. For each Critical or Important finding `{n}`:
### Combined Recommendations tables 1. **Generate motion code.** Read the audited code, the relevant lens reference (`emil-kowalski.md` / `jakub-krehel.md` / `jhey-tompkins.md`), and `references/motion-cookbook.md` for the concrete recipe. Author a `@keyframes m{n}` block and a `.demo-{n}__mt { animation: m{n} 3s {easing} infinite; }` rule.
2. **Loop pacing.** `animation-duration: 3s`. Keyframes at `0%` / `~60%` / `100%`. Motion completes by ~60% (~1.8s), then holds until `100%` (~1.2s) before looping. The `100%` state MUST match the motion-target's default (no-animation) static rendering — this is the `prefers-reduced-motion` fallback contract.
After the three per-lens sections, render severity-grouped tables for quick scanning: 3. **Inject into `<style>`.** Append the `@keyframes m{n}` + `.demo-{n}__mt` block to the report's `<style>`, after the layout CSS, inside a `@media (prefers-reduced-motion: no-preference) { ... }` guard.
4. **Inject demo-card markup.** Append the `.demo` block to the finding's `.finding-row`. Set `.demo__title` to a short motion title (e.g., "Quick tab crossfade"). Set `.demo__timing` to duration + easing (e.g., "180ms · ease-out").
```html 5. **Honor reduced-motion.** The shell's `@media (prefers-reduced-motion: reduce)` block disables all `[class*="__mt"]` animations and hides the `↻` loop indicator. The per-finding `100%` keyframe state must match the motion-target's default static rendering. Do NOT write per-finding overrides inside the reduce-motion block.
<section class="recommendations" id="critical-findings">
<h2>🔴 Critical (Must Fix)</h2>
<table>
<thead>
<tr><th>Issue</th><th>File</th><th>Action</th></tr>
</thead>
<tbody>
<tr>
<td>{issue}</td>
<td><code>{file:line}</code></td>
<td>{fix}</td>
</tr>
</tbody>
</table>
</section>
<section class="recommendations" id="important-findings">
<h2>🟡 Important (Should Fix)</h2>
<!-- same shape -->
</section>
<section class="recommendations" id="opportunity-findings">
<h2>🟢 Opportunities (Could Enhance)</h2>
<!-- same shape -->
</section>
```
The hero's severity counts link to these section IDs (`#critical-findings`, `#important-findings`, `#opportunity-findings`).
### Lens Reference Summary (closing)
```html
<section class="reference-summary">
<h2>Lens Reference Summary</h2>
<p><strong>Which lens was referenced most:</strong> {Designer} — {Perspective}</p>
<p><strong>Why:</strong> {one-line context reason}</p>
<p><strong>If you want to lean differently:</strong></p>
<ul>
<li>To follow Emil more strictly: {specific actions}</li>
<li>To follow Jakub more strictly: {specific actions}</li>
<li>To follow Jhey more strictly: {specific actions}</li>
</ul>
</section>
```
### Empty-state behavior ### Empty-state behavior
When the audit produces zero Critical + zero Important findings: When the audit produces zero Critical + zero Important findings:
- The hero still renders with the severity counts row (showing `Critical: 0 · Important: 0 · Opportunities: N`). - Header still renders with the severity counts (showing `Critical: 0 · Important: 0 · Opportunities: N`).
- Each per-lens section's `Issues to Address` block still renders its header, but the body shows a dimmed-italic line: - Each per-lens "Issues to address" `.lens-block` still renders its `.mono-label`, but the body shows a dimmed-italic line:
```html ```html
<div class="perspective-section__issues" id="emil-issues"> <p class="lens-empty">No issues found at this severity level.</p>
<h3>Issues to Address</h3>
<p class="perspective-section__empty">No issues found at this severity level.</p>
</div>
``` ```
Style: `font-style: italic; color: var(--loop-dim); padding: 12px 0;`. Communicates absence without looking broken.
- No `.finding-row` markup, no demo cards. - No `.finding-row` markup, no demo cards.
- Opportunities still render in text as usual. - Opportunities still render as `.lens-list.opp` lists.
- Combined recommendations tables render with empty `<tbody>` containing a single dimmed-italic row, OR are omitted entirely if their tier has zero findings.
### Responsive behavior ### Responsive behavior
The `.finding-row` two-column layout (prose left, demo right) needs a breakpoint for narrow viewports: - `.finding-row` 2-col → 1-col at ≤860px (demo stacks below prose).
- `.timing-grid` 2-col → 1-col at ≤860px (key list stacks below SVG).
- `.lens-table` → stacked blocks at ≤600px (each row becomes a block, headers hidden).
- `.ref-lean` 2-col → 1-col at ≤600px.
```css ### Absolute bans
.finding-row {
display: grid;
grid-template-columns: 1fr 360px;
gap: 24px;
align-items: start;
margin: 24px 0;
}
@media (max-width: 768px) { - **NO `border-left` or `border-right` >1px as a colored accent stripe** on cards, list items, callouts, or alerts. Use full borders, leading numbers, tinted backgrounds, or no visual indicator instead. (The previous version of this spec had `border-left: 3px solid var(--accent)` on the lens-take block — removed.)
.finding-row { - **NO gradient text** (`background-clip: text` + gradient).
grid-template-columns: 1fr; - **NO pulsing UI** in any demo (looping scale/opacity on status indicators, "live" pulse rings, breathing CTAs). Demo motion is structured one-shot enters that loop, not attention-getting pulses.
} - **NO chromatic accent in the default system.** The neutral-default is the supported configuration. Repointing `--accent` to a sampled brand color is permitted only when severity-hue clearance is verified.
.demo-card {
max-width: 100%;
}
}
```
Below 768px the demo card stacks below the finding prose. The shell's `min-width: 280px` keeps the card from compressing past usability.
### Report layout tokens
These extend the shell's tokens for report-level structure. Append to the `<style>` block after the shell's variables:
```css
body {
max-width: 960px;
margin: 0 auto;
padding: 48px 24px;
}
.report-hero h1 {
font-size: 1.75rem;
margin: 0 0 4px;
}
.report-hero__date {
font-family: var(--mono);
color: var(--loop-dim);
font-size: 0.875rem;
margin: 0 0 16px;
}
.report-hero__counts {
font-size: 1rem;
margin: 0 0 8px;
}
.report-hero__counts a {
color: inherit;
text-decoration: none;
border-bottom: 1px dashed var(--border);
}
.report-hero__counts a:hover {
border-bottom-style: solid;
}
.report-hero__primary {
font-family: var(--mono);
color: var(--loop-dim);
font-size: 0.875rem;
margin: 0;
}
.report-assessment {
margin: 32px 0;
font-size: 1.05rem;
line-height: 1.6;
}
.perspective-section {
margin: 48px 0;
padding-top: 24px;
border-top: 1px solid var(--border);
}
.perspective-section h2 {
font-size: 1.25rem;
margin: 0 0 16px;
}
.perspective-section__quote {
margin-top: 24px;
padding: 12px 16px;
background: var(--bg);
border-left: 3px solid var(--accent);
font-size: 0.95rem;
}
.recommendations {
margin: 48px 0;
}
.recommendations table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
.recommendations th,
.recommendations td {
text-align: left;
padding: 8px 12px;
border-bottom: 1px solid var(--border);
vertical-align: top;
}
```
--- ---
@@ -333,148 +265,73 @@ body {
When the user passes `--terminal` / `--inline` / a natural-language equivalent, do not write an HTML file. Render the decorated-markdown report inline in the conversation. When the user passes `--terminal` / `--inline` / a natural-language equivalent, do not write an HTML file. Render the decorated-markdown report inline in the conversation.
### Quick Summary (Show First) ### Quick Summary (show first)
``` ```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 AUDIT SUMMARY 📊 AUDIT SUMMARY
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔴 [X] Critical | 🟡 [X] Important | 🟢 [X] Opportunities 🔴 [X] Critical | 🟡 [X] Important | 🟢 [X] Opportunities
Primary perspective: [Designer(s)] ([context reason]) Primary lens: [Designer] ([context reason])
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
``` ```
### Overall Assessment ### Overall Assessment
One paragraph: Does this feel polished? Too much? Too little? What's working, what's not? One paragraph: Does this feel polished? Too much? Too little? What's working, what's not?
---
### Per-Designer Sections ### Per-Designer Sections
#### Emil's Section For each designer (Emil, Jakub, Jhey — ordered by weighting), use a horizontal-rule header and the body format:
``` ```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚡ EMIL KOWALSKI — Restraint & Speed ⚡ EMIL KOWALSKI — Restraint & Speed (Secondary)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
*Weight based on context. Heavy for productivity tools, light for creative/kids apps.* What's Working Well
**What to Check:**
- High-frequency interactions that might not need animation
- Keyboard-initiated actions that animate (generally shouldn't)
- Durations **if this is a productivity context** (Emil prefers under 300ms)
- Animations starting from scale(0) (should be 0.9+)
- Transform-origin on dropdowns/popovers
- CSS keyframes that should be transitions (for interruptibility)
**Body format:**
**What's Working Well**
- ✓ [Observation] — `file.tsx:line` - ✓ [Observation] — `file.tsx:line`
**Issues to Address** Issues to Address
- ✗ [Issue] — `file.tsx:line` - ✗ [Issue] — `file.tsx:line`
[Brief explanation] [Brief explanation]
Recommended: [Brief recommendation]
**Through Emil's lens**: [1-2 sentence summary] Through Emil's lens: [1–2 sentence summary]
---
#### Jakub's Section
``` ```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🎯 JAKUB KREHEL — Production Polish
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
**What to Check:**
- Enter animations (opacity + translateY + blur?)
- Exit animations (subtler than enters? Or missing entirely?)
- **Motion gaps** — Conditional renders without AnimatePresence (from gap analysis)
- **Layout transitions** — Size/position changes that snap instead of animate
- Shadow vs border usage on varied backgrounds
- Optical alignment (buttons with icons, play buttons)
- Hover state transitions (150-200ms minimum)
- Icon swap animations (opacity + scale + blur)
- Spring usage (bounce: 0 for professional, higher for playful)
**Body format:**
**What's Working Well**
- ✓ [Observation] — `file.tsx:line`
**Issues to Address**
- ✗ [Issue] — `file.tsx:line`
[Brief explanation]
**Through Jakub's lens**: [1-2 sentence summary]
---
#### Jhey's Section
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✨ JHEY TOMPKINS — Experimentation & Delight
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
**What to Check:**
- Could @property enable smoother animations?
- Could linear() provide better easing curves?
- Are stagger effects using optimal techniques?
- Could scroll-driven animations improve the experience?
- What playful touches would enhance engagement?
- Are there celebration moments that need more delight? (streaks, achievements, etc.)
**Body format:**
**What's Working Well**
- ✓ [Observation] — `file.tsx:line`
**Opportunities**
- 💡 [Idea] — `file.tsx:line`
[Brief explanation]
**Through Jhey's lens**: [1-2 sentence summary]
---
### Combined Recommendations ### Combined Recommendations
**Critical (Must Fix)** Three severity tables:
| | Issue | File | Action |
|-|-------|------|--------| ```
Critical · Must Fix
| | Issue | File | Fix |
|-|-------|------|-----|
| 🔴 | [Issue] | `file:line` | [Fix] | | 🔴 | [Issue] | `file:line` | [Fix] |
**Important (Should Fix)** Important · Should Fix
| | Issue | File | Action | | | Issue | File | Fix |
|-|-------|------|--------| |-|-------|------|-----|
| 🟡 | [Issue] | `file:line` | [Fix] | | 🟡 | [Issue] | `file:line` | [Fix] |
**Opportunities (Could Enhance)** Opportunities · Could Enhance
| | Enhancement | Where | Impact | | | Enhancement | Where | Impact |
|-|-------------|-------|--------| |-|-------------|-------|--------|
| 🟢 | [Enhancement] | `file:line` | [Impact] | | 🟢 | [Enhancement] | `file:line` | [Impact] |
```
---
### Lens Reference Summary ### Lens Reference Summary
End every terminal audit with: ```
> Lens referenced most: [Designer Name] — [Perspective]
> **Which lens was referenced most**: [Designer Name] — [Perspective]
> >
> **Why**: [Explanation based on the project context] > Why: [Explanation based on the project context]
> >
> **If you want to lean differently**: > If you want to lean differently:
> - To follow Emil more strictly: [specific actions] > - To follow Emil more strictly: [specific actions]
> - To follow Jakub more strictly: [specific actions] > - To follow Jakub more strictly: [specific actions]
> - To follow Jhey more strictly: [specific actions] > - To follow Jhey more strictly: [specific actions]
```
--- ---
@@ -486,4 +343,4 @@ Default to HTML mode. Trigger terminal mode only when the user explicitly signal
- Natural-language equivalent: "show the full report inline," "skip the HTML," "no HTML," "terminal only" - Natural-language equivalent: "show the full report inline," "skip the HTML," "no HTML," "terminal only"
- Any headless or CI environment where opening a browser doesn't apply - Any headless or CI environment where opening a browser doesn't apply
When in doubt, render HTML and mention the terminal-mode flag in the 3-line summary (see `workflows/audit.md` STEP 3) so the user knows the alternative exists. When defaulting to HTML, mention in the 3-line confirmation summary (see `workflows/audit.md`) that `--terminal` is the alternative — so the user knows it exists.
File diff suppressed because it is too large Load Diff
+50 -13
View File
@@ -65,8 +65,16 @@ echo ""
echo "── Updating GStack submodule..." echo "── Updating GStack submodule..."
warn "GStack tracks branch = main (no commit hash). Review upstream commits before updating." warn "GStack tracks branch = main (no commit hash). Review upstream commits before updating."
echo "" echo ""
printf " Proceed with GStack update? [y/N] " # TTY guard: in a non-interactive run (cron, CI, background shell) `read`
read -r _gstack_confirm # hits EOF and dies under set -e — the whole update aborted mid-script.
# Default to the safe N and keep going; interactive behavior unchanged.
if [ -t 0 ]; then
printf " Proceed with GStack update? [y/N] "
read -r _gstack_confirm
else
info "Non-interactive run — skipping GStack update (run in a terminal to be prompted)"
_gstack_confirm="n"
fi
if [[ "$_gstack_confirm" =~ ^[Yy]$ ]]; then if [[ "$_gstack_confirm" =~ ^[Yy]$ ]]; then
# Capture gstack state before the update so we can restore it after # Capture gstack state before the update so we can restore it after
# ./setup runs (setup re-creates every symlink; without this, an # ./setup runs (setup re-creates every symlink; without this, an
@@ -117,6 +125,13 @@ fi
# ── 3. Update RTK (if pinned version available) ── # ── 3. Update RTK (if pinned version available) ──
echo "" echo ""
echo "── Updating RTK..." echo "── Updating RTK..."
# cargo lives in ~/.cargo/bin, which hand-managed profiles lose (BLK-016
# class) — source cargo env, as install-plugins.sh does, before concluding
# cargo is absent. Without this the step silently never updated rtk.
if ! command -v cargo &>/dev/null && [ -f "$HOME/.cargo/env" ]; then
# shellcheck disable=SC1091
source "$HOME/.cargo/env"
fi
if command -v cargo &>/dev/null; then if command -v cargo &>/dev/null; then
RTK_VERSION="" RTK_VERSION=""
if [ -f "$REPO/plugins.lock.json" ] && command -v python3 &>/dev/null; then if [ -f "$REPO/plugins.lock.json" ] && command -v python3 &>/dev/null; then
@@ -128,21 +143,44 @@ print(d.get('rtk',{}).get('version',''))
" 2>/dev/null || true) " 2>/dev/null || true)
fi fi
# Version-jump guard: a cargo build takes minutes — only pay it when the
# target (pin, or the newest remote tag for "latest") differs from what is
# installed. Same pin-honored/skip-on-match shape as the semgrep step.
RTK_CUR=$(rtk --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true)
[ -z "$RTK_CUR" ] && RTK_CUR=$("$HOME/.cargo/bin/rtk" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true)
if [ -n "$RTK_VERSION" ] && [ "$RTK_VERSION" != "latest" ]; then if [ -n "$RTK_VERSION" ] && [ "$RTK_VERSION" != "latest" ]; then
info "Pinned version: $RTK_VERSION" if [ "${RTK_VERSION#v}" = "$RTK_CUR" ]; then
info "Compiling from source — this may take a few minutes..." ok "rtk already at pinned $RTK_CUR"
if cargo install --git https://github.com/rtk-ai/rtk --tag "$RTK_VERSION" --force; then
ok "RTK updated to $RTK_VERSION"
else else
warn "RTK update failed" info "Pinned version: $RTK_VERSION (installed: ${RTK_CUR:-none})"
info "Compiling from source — this may take a few minutes..."
if cargo install --git https://github.com/rtk-ai/rtk --tag "$RTK_VERSION" --force; then
ok "RTK updated to $RTK_VERSION"
else
warn "RTK update failed"
fi
fi fi
else else
info "No pinned version — installing latest" # "latest" = newest release TAG, resolved by name and installed BY TAG.
info "Compiling from source — this may take a few minutes..." # (A bare `cargo install --git` builds the default-branch HEAD, whose
if cargo install --git https://github.com/rtk-ai/rtk --force; then # Cargo.toml version can trail the newest tag — the guard would then
ok "RTK updated (latest)" # never converge and recompile on every run.)
RTK_TIP_TAG=$(git ls-remote --tags https://github.com/rtk-ai/rtk 2>/dev/null \
| sed -n 's|.*refs/tags/\(v\{0,1\}[0-9][0-9.]*\)$|\1|p' | sort -V | tail -1 || true)
RTK_TIP="${RTK_TIP_TAG#v}"
if [ -n "$RTK_TIP" ] && [ "$RTK_TIP" = "$RTK_CUR" ]; then
ok "rtk already at latest tag ($RTK_CUR)"
else else
warn "RTK update failed" info "No pin — latest tag: ${RTK_TIP_TAG:-unknown} (installed: ${RTK_CUR:-none})"
info "Compiling from source — this may take a few minutes..."
if [ -n "$RTK_TIP_TAG" ] && cargo install --git https://github.com/rtk-ai/rtk --tag "$RTK_TIP_TAG" --force; then
ok "RTK updated to $RTK_TIP_TAG"
elif [ -z "$RTK_TIP_TAG" ] && cargo install --git https://github.com/rtk-ai/rtk --force; then
ok "RTK updated (latest HEAD — no tag resolvable)"
else
warn "RTK update failed"
fi
fi fi
fi fi
else else
@@ -388,7 +426,6 @@ echo "── Updating external skills (npx skills)..."
if command -v npx &>/dev/null; then if command -v npx &>/dev/null; then
NPX_SKILLS=( NPX_SKILLS=(
"alchaincyf/darwin-skill" "alchaincyf/darwin-skill"
"alchaincyf/find-skills"
) )
for _src in "${NPX_SKILLS[@]}"; do for _src in "${NPX_SKILLS[@]}"; do
_name="${_src##*/}" _name="${_src##*/}"