astack-skills 0.9.0 → 0.10.0

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astack",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Skills for Claude Code and Codex that take product work from the thread to a proven, live release.",
5
5
  "author": {
6
6
  "name": "Andrew Wilkinson",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astack",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Skills for Claude Code and Codex that take product work from the thread to a proven, live release.",
5
5
  "author": {
6
6
  "name": "Andrew Wilkinson",
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.0
4
+
5
+ Motion work beyond a single animation, prompted by a read of [Emil Kowalski's skills](https://github.com/emilkowalski/skills) (MIT) as presented on aiforui.dev. Each idea is folded into an existing skill; no skill was added.
6
+
7
+ - `web-design` motion gains a short effect vocabulary for vague requests, choreography roles for several elements moving at once (one lead, followers 30-80ms behind, named still elements, exits together and faster), and a whole-app motion audit: an inventory with `file:line`, keep, fix or cut by frequency and purpose, a default cut list, the missing motion, and self-contained change items. Motion reviews end with approve, approve with fixes, or block.
8
+ - `web-design` prototypes two or three variants behind a development-only switch when two directions both survive the generic test, and uses a maintained headless library (or the project's primitive) for toasts, drawers, comboboxes, date pickers, virtualised lists and drag and drop instead of hand-rolling them.
9
+ - The craft floor adds absolute dates after about a week, `Intl.PluralRules` and `Intl.NumberFormat` for counts, and eased multi-stop edge fades.
10
+ - `catalog.json` carries a stage, the skills each one pairs with and example prompts, checked by `bun run validate`. The new skills directory at [andrewwilkinson.io/skills](https://andrewwilkinson.io/skills/) is built from it.
11
+ - README and installation docs add the `npx skills add ADWilkinson/astack` route, which the skills CLI already resolves to all eleven skills.
12
+ - Two routing eval cases for app-wide motion audits and choreography.
13
+
3
14
  ## 0.9.0
4
15
 
5
16
  Lessons from taking a real product through a brand, scale and claims pass.
package/README.md CHANGED
@@ -30,6 +30,15 @@ codex plugin marketplace add ADWilkinson/astack
30
30
  codex plugin add astack@adwilkinson
31
31
  ```
32
32
 
33
+ With the [skills](https://skills.sh) CLI, which also covers Cursor, Gemini CLI and other agents:
34
+
35
+ ```sh
36
+ npx skills@latest add ADWilkinson/astack
37
+ npx skills@latest add ADWilkinson/astack --skill web-design
38
+ ```
39
+
40
+ Browse every skill, with example prompts and what it pairs with, at [andrewwilkinson.io/skills](https://andrewwilkinson.io/skills/).
41
+
33
42
  Or do what I do and paste this repo's link to your agent with "install this". It reads the same instructions you just did.
34
43
 
35
44
  ## Get started
package/catalog.json CHANGED
@@ -4,67 +4,204 @@
4
4
  "name": "astack",
5
5
  "title": "astack",
6
6
  "summary": "Own a product task up to a proven, review-ready PR",
7
- "example": "astack this: add annual pricing with tests, mobile checks and tracking."
7
+ "example": "astack this: add annual pricing with tests, mobile checks and tracking.",
8
+ "stage": "own",
9
+ "pairs": [
10
+ "context",
11
+ "dogfood",
12
+ "ship"
13
+ ],
14
+ "prompts": [
15
+ "astack this: add annual pricing to the billing page, phones matter most",
16
+ "Own the onboarding fix end to end and open a PR I can merge",
17
+ "Take this issue from the thread to a reviewed PR with tests and tracking"
18
+ ]
8
19
  },
9
20
  {
10
21
  "name": "oneshot",
11
22
  "title": "oneshot",
12
23
  "summary": "Idea to merged, deployed, verified live and announced",
13
- "example": "oneshot this: add annual pricing and take it all the way to production."
24
+ "example": "oneshot this: add annual pricing and take it all the way to production.",
25
+ "stage": "own",
26
+ "pairs": [
27
+ "astack",
28
+ "ship",
29
+ "launch"
30
+ ],
31
+ "prompts": [
32
+ "oneshot this: add annual pricing and get it live",
33
+ "oneshot the empty-state fix and tell me when production serves it",
34
+ "oneshot this while I'm out: merge, deploy and post the update"
35
+ ]
14
36
  },
15
37
  {
16
38
  "name": "context",
17
39
  "title": "Context",
18
40
  "summary": "Gather and reconcile what changes the task",
19
- "example": "Read the Slack thread, the issue and the analytics before we touch checkout."
41
+ "example": "Read the Slack thread, the issue and the analytics before we touch checkout.",
42
+ "stage": "before",
43
+ "pairs": [
44
+ "astack",
45
+ "dogfood"
46
+ ],
47
+ "prompts": [
48
+ "Read the Slack thread and the issue before we touch checkout",
49
+ "Pick up this branch: what was decided and what is still open?",
50
+ "What changed since the last PR on this ticket?"
51
+ ]
20
52
  },
21
53
  {
22
54
  "name": "web-design",
23
55
  "title": "Web design",
24
56
  "summary": "Design UI and motion that look like this product, not AI",
25
- "example": "This page looks AI-generated. Give it a real direction and check mobile and dark."
57
+ "example": "This page looks AI-generated. Give it a real direction and check mobile and dark.",
58
+ "stage": "build",
59
+ "pairs": [
60
+ "deslop",
61
+ "web-graphics",
62
+ "dogfood"
63
+ ],
64
+ "prompts": [
65
+ "This page looks AI-generated. Give it a real direction",
66
+ "Audit every animation in the app and cut the ones that slow people down",
67
+ "The drawer stutters on my phone, find out why"
68
+ ]
26
69
  },
27
70
  {
28
71
  "name": "web-graphics",
29
72
  "title": "Web graphics",
30
73
  "summary": "Build bespoke isometric, flat and ASCII graphics",
31
- "example": "Make an isometric hero showing three inboxes merging into one queue."
74
+ "example": "Make an isometric hero showing three inboxes merging into one queue.",
75
+ "stage": "build",
76
+ "pairs": [
77
+ "web-design",
78
+ "launch"
79
+ ],
80
+ "prompts": [
81
+ "Make an isometric hero showing three inboxes merging into one queue",
82
+ "Animated sticker icons for the onboarding steps, in our brand blue",
83
+ "A share card and favicon set drawn in code"
84
+ ]
32
85
  },
33
86
  {
34
87
  "name": "deslop",
35
88
  "title": "Deslop",
36
89
  "summary": "Strip AI tells from copy, UI and code",
37
- "example": "Deslop the landing page copy and components, keep the facts and voice."
90
+ "example": "Deslop the landing page copy and components, keep the facts and voice.",
91
+ "stage": "build",
92
+ "pairs": [
93
+ "web-design",
94
+ "launch"
95
+ ],
96
+ "prompts": [
97
+ "This sounds like AI. Fix it and keep the facts",
98
+ "Learn my voice from these posts, then rewrite the launch email",
99
+ "Strip the generated tells from the landing page components"
100
+ ]
38
101
  },
39
102
  {
40
103
  "name": "instrument",
41
104
  "title": "Instrument",
42
105
  "summary": "Track the outcome and prove the event arrives",
43
- "example": "Track first listing published and give me a funnel I can trust."
106
+ "example": "Track first listing published and give me a funnel I can trust.",
107
+ "stage": "build",
108
+ "pairs": [
109
+ "dogfood",
110
+ "launch"
111
+ ],
112
+ "prompts": [
113
+ "Track first listing published and give me a funnel I can trust",
114
+ "Signups on the dashboard dropped on Tuesday, check the tracking",
115
+ "Prove the purchase event fires once, on real success"
116
+ ]
44
117
  },
45
118
  {
46
119
  "name": "discoverability",
47
120
  "title": "Discoverability",
48
121
  "summary": "Fix why search and AI answers miss the product",
49
- "example": "Audit why ChatGPT and Google don't surface us for 'best invoicing app for freelancers'."
122
+ "example": "Audit why ChatGPT and Google don't surface us for 'best invoicing app for freelancers'.",
123
+ "stage": "prove",
124
+ "pairs": [
125
+ "launch",
126
+ "deslop"
127
+ ],
128
+ "prompts": [
129
+ "Why doesn't ChatGPT mention us for invoicing apps?",
130
+ "Our pricing page dropped out of Google, find out why",
131
+ "Check what crawlers and link previews actually see"
132
+ ]
50
133
  },
51
134
  {
52
135
  "name": "dogfood",
53
136
  "title": "Dogfood",
54
137
  "summary": "Use the product for real, fix what breaks",
55
- "example": "Dogfood onboarding on my signed-in account on mobile and fix what you hit."
138
+ "example": "Dogfood onboarding on my signed-in account on mobile and fix what you hit.",
139
+ "stage": "prove",
140
+ "pairs": [
141
+ "web-design",
142
+ "instrument",
143
+ "ship"
144
+ ],
145
+ "prompts": [
146
+ "Use onboarding like a customer on my phone and fix what breaks",
147
+ "Walk checkout signed in before launch",
148
+ "Does the page promise anything the product does not do?"
149
+ ]
56
150
  },
57
151
  {
58
152
  "name": "ship",
59
153
  "title": "Ship",
60
154
  "summary": "Get to green, merge, deploy and prove it is live",
61
- "example": "Ship this: clear the review threads and CI, deploy, verify production."
155
+ "example": "Ship this: clear the review threads and CI, deploy, verify production.",
156
+ "stage": "deliver",
157
+ "pairs": [
158
+ "dogfood",
159
+ "launch"
160
+ ],
161
+ "prompts": [
162
+ "Ship it: clear CI and the review threads, deploy, verify production",
163
+ "Babysit this PR until it merges",
164
+ "They said it shipped. Is production actually serving it?"
165
+ ]
62
166
  },
63
167
  {
64
168
  "name": "launch",
65
169
  "title": "Launch",
66
170
  "summary": "Ready the page, kit, posts and tracker for launch",
67
- "example": "Prepare the launch: share card, page audit, posts, directories and tracking."
171
+ "example": "Prepare the launch: share card, page audit, posts, directories and tracking.",
172
+ "stage": "deliver",
173
+ "pairs": [
174
+ "web-graphics",
175
+ "deslop",
176
+ "discoverability"
177
+ ],
178
+ "prompts": [
179
+ "We launch tomorrow: share card, page check, posts and a tracker",
180
+ "Write the Show HN post and the X thread from what actually shipped",
181
+ "Which directories should we list on, and how will we read the results?"
182
+ ]
183
+ }
184
+ ],
185
+ "stages": [
186
+ {
187
+ "id": "own",
188
+ "title": "Own it end to end"
189
+ },
190
+ {
191
+ "id": "before",
192
+ "title": "Before you build"
193
+ },
194
+ {
195
+ "id": "build",
196
+ "title": "Build"
197
+ },
198
+ {
199
+ "id": "prove",
200
+ "title": "Prove it works"
201
+ },
202
+ {
203
+ "id": "deliver",
204
+ "title": "Deliver"
68
205
  }
69
206
  ]
70
207
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astack-skills",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "private": false,
5
5
  "description": "Skills for Claude Code and Codex that take product work from the thread to a proven, live release.",
6
6
  "license": "MIT",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "astack",
4
- "version": "0.9.0",
4
+ "version": "0.10.0",
5
5
  "description": "Skills for Claude Code and Codex that take product work from the thread to a proven, live release.",
6
6
  "author": {
7
7
  "name": "Andrew Wilkinson",
@@ -42,16 +42,19 @@ Choose from the subject's world: its materials, places, artefacts and vocabulary
42
42
 
43
43
  When the user gives references, study them properly: [reference study](references/reference-study.md) pulls computed styles from the live sites so you borrow relationships (scale, rhythm, density), not surface decoration.
44
44
 
45
+ When two directions both survive the generic test and the choice is hard to argue from a plan, prototype instead of debating: build two or three variants of just the contested piece behind a development-only switch (`?variant=b`), shoot each at the same widths and states, choose with the user or the rubric, and delete the losers before finishing. Variants differ in one decision each (layout, type pairing, density), so the comparison says something.
46
+
45
47
  ## 3. Build in the product's system
46
48
 
47
49
  - Use existing tokens, primitives and components. Add a token before adding a one-off value.
50
+ - Components that are hard to get right by hand (toasts, drawers and sheets, comboboxes, date pickers, virtualised lists, drag and drop) come from the project's primitive. If it has none and a dependency is in scope, choose a maintained, accessible, headless library over hand-rolling one: check its last release, open issues, bundle size and keyboard support first, and record the choice in DESIGN.md.
48
51
  - Real content only: real names, long names, real numbers, empty data. Never lorem ipsum, never invented metrics to fill a layout. Before calling a data-driven screen done, run it on [worst-case data](references/worst-case-data.md): the longest plausible name, an unbreakable email, a count of 1 and of 1,284, an empty list, a missing avatar. A workspace also runs at the plan limit, with seeded records: [workspace at scale](references/workspace-scale.md).
49
52
  - Design the states with the screen: loading, empty, error, success, disabled, overflow. An empty state that looks broken is a bug. A loading state is the loaded layout with content replaced by bones of the same boxes. A shorter stack of bars is a jump. Mark the stable regions and compare the two states: [loading](references/loading.md).
50
53
  - Hide what has nothing to say. Zero rows, zero balances and empty charts become an honest empty state or disappear; no walls of zeros.
51
54
  - Headers are labels. A subtitle that re-explains the header gets deleted.
52
55
  - Mobile is its own layout: reorder by priority, use horizontal snap rails where desktop uses grids, `dvh`/`svh` not `100vh`, inputs at 16px or larger, tap targets 44px. Anything people use on a phone gets the [mobile baseline](references/mobile.md): no tap flash, hover gated to real pointers, press feedback on touch, safe areas, no zoom on focus.
53
56
 
54
- Hold the line on the [craft floor](references/craft-floor.md) while building, not as a final pass. For any animation or transition, read [motion](references/motion.md) first: decide whether it should animate at all (never for keyboard or high-frequency actions), ease-out and under 300ms for UI, press feedback and origin-aware popovers, interruptible transitions, only `transform` and `opacity`, reduced motion handled, and watched as a filmstrip. The same reference diagnoses janky animation by cause.
57
+ Hold the line on the [craft floor](references/craft-floor.md) while building, not as a final pass. For any animation or transition, read [motion](references/motion.md) first: decide whether it should animate at all (never for keyboard or high-frequency actions), ease-out and under 300ms for UI, press feedback and origin-aware popovers, interruptible transitions, only `transform` and `opacity`, reduced motion handled, and watched as a filmstrip. The same reference names effects when the request is vague, gives several moving elements one lead, diagnoses janky animation by cause, and audits a whole app's motion into keep, fix and cut with `file:line` and a verdict.
55
58
 
56
59
  ## 4. Look at it
57
60
 
@@ -12,6 +12,13 @@ The minimum for anything that ships. Check these while building, then once more
12
12
  - No middle-dot strings ("A · B · C") in running text: they strand at line ends. Use separate elements with gaps.
13
13
  - Tabular numerals (`font-variant-numeric: tabular-nums`) for money, times, counts and any column of numbers.
14
14
  - Display text no larger than it needs to be: a 120px headline is a layout, not emphasis.
15
+ - Relative times stop being relative after about a week: "3 hours ago" and "Yesterday", then "4 Nov" and "4 Nov 2025". Nobody can place "340 days ago". Keep the exact timestamp in a `title` or `<time datetime>`.
16
+ - Counts read as words: `Intl.PluralRules` (or the i18n library) for "1 member" and "2 members", and `Intl.NumberFormat` for separators and compact forms.
17
+
18
+ ## Edges and fades
19
+
20
+ - A fade over scrolling content or at a list's edge uses an eased gradient with several stops (or a `mask-image` with the same stops), not a two-stop linear gradient, which shows a visible band where it ends.
21
+ - A fade sits over content only where more content exists; hide it at the start and end of the scroll.
15
22
 
16
23
  ## Colour and contrast
17
24
 
@@ -27,6 +27,21 @@ For each animated thing, one line per field:
27
27
  - **Interruption**: what happens on a second click mid-animation: reverse, retarget or finish.
28
28
  - **Reduced motion**: what shows instead (see [reduced motion](#reduced-motion-wiring)).
29
29
 
30
+ When the request is vague ("make it feel smoother", "something like Linear's"), name the effect before building it, and confirm the name when the choice changes the work:
31
+
32
+ | Name | What moves | Reach for it when |
33
+ | --- | --- | --- |
34
+ | Fade | Opacity only | State appears or leaves in place; the reduced-motion fallback for most others |
35
+ | Scale-in | `scale(0.95)` and opacity to rest, from the trigger | Popovers, menus, tooltips |
36
+ | Slide | Translate along one axis, by the element's own size | Sheets, drawers, toasts, pages in a stack |
37
+ | Crossfade | Two states overlap while one fades out | Swapping an icon, label or image in the same slot |
38
+ | Morph (shared element) | One element changes size and position into its next state | A card opening into a detail view, a tab pill moving |
39
+ | Reveal | `clip-path` or mask uncovers content in place | Headlines, comparison sliders, hold-to-confirm fills |
40
+ | Stagger | The same entrance offset per item | A list or grid arriving once |
41
+ | Spring | Physics with velocity, can overshoot | Gestures, drag release, anything interrupted often |
42
+ | Scrub | Progress tied to scroll or a pointer | Explainers and scroll-linked stories |
43
+ | Follow-through | Parts settle slightly after the lead stops | Playful brands only, small amplitude |
44
+
30
45
  ### 3. Pick the ease and the duration
31
46
 
32
47
  Ease by what is happening:
@@ -88,7 +103,24 @@ Match the motion to the component's character: a dashboard is crisp and fast; a
88
103
  - Ambient loops pause offscreen (`IntersectionObserver`) and when the tab is hidden (`visibilitychange`). At most one ambient effect per screen.
89
104
  - Never delay the actual action (navigation, submit) until an animation finishes.
90
105
 
91
- ### 6. Gestures
106
+ ### 6. Choreograph several elements
107
+
108
+ When more than one thing moves for the same change (a dialog and its backdrop, a card expanding while its list dims, a step advancing), give each element one role before writing timings:
109
+
110
+ - **Lead**: the element the change is about. Exactly one. It starts first and carries the signature ease.
111
+ - **Follow**: elements that move because the lead did (contents inside a sheet, siblings making room). They start 30–80ms after the lead, travel less, and finish no later than it does.
112
+ - **Parallel**: elements that change for the same reason at the same time (backdrop and dialog). Same start, same duration, same ease family.
113
+ - **Still**: everything else. Name it. Most of the screen should not move, and an element that moves for no reason reads as a bug.
114
+
115
+ Then hold the sequence together:
116
+
117
+ - The whole sequence for an ordinary UI change ends within about 400ms; followers do not extend it.
118
+ - Exits run together and faster than the entrance, in reverse role order or all at once. Nobody waits for a staggered exit.
119
+ - Spatial sense stays consistent: things that came from the trigger return to it, a sheet that rose from the bottom leaves downwards.
120
+ - Input is accepted from the first frame. If the lead is interrupted, followers retarget with it rather than finishing their own timelines.
121
+ - Write the roles into the motion contract, one line per element, so a reviewer can check the filmstrip against it.
122
+
123
+ ### 7. Gestures
92
124
 
93
125
  - Dismiss on velocity as well as distance: `Math.abs(distance) / elapsedMs > 0.11` is a flick, and a flick is enough.
94
126
  - Past a boundary, apply damping (the further the drag, the less it moves) instead of a hard stop.
@@ -96,7 +128,7 @@ Match the motion to the component's character: a dashboard is crisp and fast; a
96
128
  - Declare which axes the gesture owns with `touch-action` (`pan-y` on a horizontal carousel, `none` only on a surface that handles every direction).
97
129
  - Decorative pointer-following goes through a spring (Motion's `useSpring`, or `{ type: "spring", duration: 0.5, bounce: 0.2 }`), never raw pointer coordinates. Functional readouts follow the pointer directly.
98
130
 
99
- ### 7. Keep every frame on time
131
+ ### 8. Keep every frame on time
100
132
 
101
133
  The budget is 16.7ms a frame at 60Hz and 8ms at 120Hz, which most phones now are. Design against the smaller number and a mid-range phone, not the laptop.
102
134
 
@@ -114,7 +146,7 @@ The budget is 16.7ms a frame at 60Hz and 8ms at 120Hz, which most phones now are
114
146
 
115
147
  Name the cause before changing code, and say which findings came from reading code and which from a recording. A performance trace in the browser beats rewriting by feel; report frame rates only when measured, with the device.
116
148
 
117
- ### 8. Watch it play
149
+ ### 9. Watch it play
118
150
 
119
151
  Stills cannot prove motion. Capture a filmstrip and look at the frames:
120
152
 
@@ -134,14 +166,28 @@ document.getAnimations().filter(a => a.playState === "running").length
134
166
 
135
167
  Anything above zero that is not a deliberate indicator is a loop. On a product page, prefer one finite story over ambient movement: the thing arrives, the result appears, the next action is invited.
136
168
 
137
- ### 9. Report
169
+ ### 10. Audit a whole app's motion
138
170
 
139
- Report the contract, the tokens used, what you watched (filmstrip paths, cases exercised) and anything measured with its method. When reviewing someone's motion, give findings as one table:
171
+ For "audit the animations", "it feels busy" or "where should this move", work from an inventory, not a page you happened to open:
140
172
 
141
- | Before | After | Why |
142
- | --- | --- | --- |
143
- | `transition: all 300ms` | `transition: transform 200ms var(--ease-out)` | Name the properties; 300ms is slow for UI |
144
- | Popover `transform-origin: center` | `transform-origin: var(--transform-origin)` | It should grow from its trigger |
173
+ 1. **Inventory.** Search the source for every moving thing: `transition`, `animation`, `@keyframes`, `@starting-style`, `animate(`, `requestAnimationFrame`, `motion.`/`<motion`, `useSpring`, `AnimatePresence`, Tailwind `transition-*`, `animate-*` and `duration-*`, and scroll or intersection observers that add classes. One row per animated element with its `file:line`.
174
+ 2. **Classify each row** by the frequency table in section 1 and by purpose. The row is **keep**, **fix** (right to move, wrong ease, duration, origin, properties or interruption) or **cut**.
175
+ 3. **Cut by default**, unless the brief or DESIGN.md asks for them: motion on keyboard-driven actions, fade-up on every section, a delay before every tooltip after the first, page-load stagger on screens people open many times a day, hover zoom on cards, ambient loops that explain nothing, and spinners on actions that finish under about 300ms.
176
+ 4. **Find the missing ones.** List the few places where motion would explain something and does not: an item that appears with no hint where it came from, a removal that leaves the user unsure it happened, a press with no feedback, a sheet that teleports. Each needs a purpose from section 1.
177
+ 5. **Write each change as a self-contained item**: `file:line`, the current behaviour, the target (ease token, duration, properties, origin, role), the reason, and how to check it (filmstrip command or the rapid-trigger case). Another agent or person should be able to carry out any one item without reading the rest.
178
+
179
+ Apply the cuts and fixes that are clearly defects; list the additions for a decision unless the request was to add them.
180
+
181
+ ### 11. Report
182
+
183
+ Report the contract, the tokens used, what you watched (filmstrip paths, cases exercised) and anything measured with its method. When reviewing someone's motion, give findings as one table, each with its `file:line`:
184
+
185
+ | Where | Before | After | Why |
186
+ | --- | --- | --- | --- |
187
+ | `card.tsx:14` | `transition: all 300ms` | `transition: transform 200ms var(--ease-out)` | Name the properties; 300ms is slow for UI |
188
+ | `popover.tsx:31` | `transform-origin: center` | `transform-origin: var(--transform-origin)` | It should grow from its trigger |
189
+
190
+ End a review with a verdict: **approve** (nothing blocks), **approve with fixes** (only polish rows) or **block** (any row breaks a rule under Refuse, gets stuck when interrupted, ignores reduced motion or drops frames on the target device). A review that lists problems and then waves the change through is not a review.
145
191
 
146
192
  For an exported video, deliver the file and check duration, dimensions, crop and audio sync; a browser preview is not an export.
147
193