
- ChatGPT
- Claude
- Gemini
28-Day Content Calendar System
Eleven chained prompts: brief, voice, pillars, hooks, four-week calendar, daily captions, weekly review.
62 results

Eleven chained prompts: brief, voice, pillars, hooks, four-week calendar, daily captions, weekly review.

Catches breaking REST changes in a PR before merge: diff vs spec, severity table, migration notes.

Paste the week's numbers; get three priorities, two avoided decisions and the risk nobody is flagging.

Ten prompts from ICP scoring to breakup: triggers, a four-line opener, three follow-ups, replies, proposal.

A complete dark SaaS landing page from your product facts: hero, features, pricing, FAQ. Editable HTML, one run.

Ten prompts from first build to launch: pick a tool, describe the app, edit safely, add payments, ship.

An eight-point check for the mistakes that get small apps breached in month one, with PASS/FAIL per item.

One subject, one action, one camera move, named light and layered audio — a teaser that reads as a commercial.

Cluster 100+ reviews into five audience segments, each with a pain, an objection and a Meta ad to test.
Use case: Product teaser (template 1) | Element | Value | |---|---| | Subject | A matte black ceramic pour-over coffee dripper with a thin polished copper rim, sitting on a clear glass carafe | | Action | 1) A thin stream of dark coffee trickles from the dripper into the carafe while soft steam curls up from the rim; 2) the dripper and carafe turn a slow quarter turn; 3) the final second holds still on the dripper's front, copper rim catching the light | | Scene | A walnut kitchen counter by a window, early morning just after sunrise, a faint haze of steam in the air, uncluttered background | | Angle / Move | Vertical close-up at counter height, slightly above eye level / slow dolly in | | Lens | 85mm-style telephoto feel, shallow depth of field, kitchen background in soft bokeh | | Light / Palette / Mood | Soft low-angle morning sunlight from a window on the left with gentle fill and a warm rim on the copper / matte black, warm copper, honey-brown walnut, soft cream highlights / calm, quiet, unhurried | | Style / Time | Premium commercial look, photorealistic / real-time pace, steady trickle throughout, still hold in the last second | | Audio | Close, clear coffee trickling into glass, a faint soft drip, quiet low morning room tone, no music | | Negative | text, logos, letters, labels, hands, people, clutter, reflections of people, plastic look | Final prompt (paste into Veo / Gemini / Flow): Premium commercial look, photorealistic, vertical close-up at counter height of a matte black ceramic pour-over coffee dripper with a thin polished copper rim, sitting on a clear glass carafe on a walnut kitchen counter by a window in the early morning just after sunrise. A thin stream of dark coffee trickles from the dripper into the carafe while soft steam curls up from the rim, and the dripper and carafe turn a slow quarter turn. Slow dolly in, 85mm-style telephoto feel, shallow depth of field with the kitchen background in soft bokeh. Soft low-angle morning sunlight from a window on the left with gentle fill and a warm rim light catching the copper, palette of matte black, warm copper, honey-brown walnut and soft cream highlights, calm and quiet mood. Real-time pace; the final second holds still on the dripper's front. Audio: close, clear sound of coffee trickling into a glass carafe, a faint soft drip, quiet low morning room tone, no music. Negative prompt: text, logos, letters, labels, hands, people, clutter, reflections of people, plastic look Settings: 9:16, 8 seconds, audio on.
Use it to turn a rough video idea into a structured Veo prompt with a filled anatomy table, camera, lens, light, audio and a negative prompt.
Project: fresh `npm init -y` + `npm i -D vitest typescript` (Vitest 5.1.0, TypeScript strict). Every runner output below is pasted from the actual run.
Behaviour list (simplest first): 1. returns empty for blank input · 2. returns the address trimmed and lowercased when valid · 3. returns malformed when there is no @ · 4. returns malformed when there is more than one @ · 5. returns malformed when the domain has no dot · 6. returns too_long when the address is over 254 characters
Cycle 1: returns empty for blank input
```
[TDD] cycle 1/6 step RED behaviour: returns empty for blank input
test: src/validateEmail.test.ts > validateEmail > returns empty for blank input
ran: npx vitest run src/validateEmail.test.ts -t "returns empty for blank input"
result: FAIL Error: Cannot find module './validateEmail' imported from <project>/src/validateEmail.test.ts
reason: right (the unit under test does not exist yet: missing function, not a typo in the test)
changed: src/validateEmail.test.ts (+8, new file)
next: minimum code in src/validateEmail.ts
```
```
[TDD] cycle 1/6 step GREEN behaviour: returns empty for blank input
ran: npx vitest run src/validateEmail.test.ts -t "returns empty for blank input" --reporter=verbose; npx vitest run src/validateEmail.test.ts
result: PASS Tests 1 passed (1)
changed: src/validateEmail.ts (+9, new file: result types + hard-coded `{ ok: false, error: 'empty' }`)
next: refactor
```
Refactor: no refactor (hard-coded return is forced into the general version by cycle 2).
[Cycles 2–6 follow the same shape: each RED shows a real assertion mismatch such as `Tests 1 failed | 1 skipped (2)`, each GREEN the passing count, with refactors in cycles 5 and 6.]
Done
```
[TDD] DONE cycles 6 tests added 6 suite PASS
ran: npm test (vitest run) and npx tsc -p .
result: Test Files 1 passed (1) | Tests 6 passed (6); tsc exit 0
pre-existing failures: none (no tests existed)
found, not fixed: the 254 boundary itself (exactly 254 chars accepted) has no test; it passes today, so a test would be green on first run. 'a@.com' and 'a@b.' are accepted because the spec only asks for a dot in the domain.
```
No-fake-green check before every GREEN: no `.skip`/`.todo`, no assertion weakened, no test-only branch, nothing mocked.Use it to make your coding agent build every change test-first: one failing test, the minimum code, a refactor, and a status block after each step.
> Assumption: signed-in users read their own orders and create their own orders as `pending`; every status change after that is made by your server with the `service_role` key, never by the browser. Audit: `public.orders` (about 1.8M rows) Rule files applied: `security-rls-basics`, `security-rls-performance`, `security-privileges`, `schema-foreign-key-indexes`, `query-missing-indexes`, `query-composite-indexes`, `schema-data-types`, `schema-constraints`, `lock-short-transactions`, `monitor-explain-analyze`. Findings, by rule (first 6 of 12) | # | Severity | Rule | Finding | |---|---|---|---| | 1 | CRITICAL | `security-rls-basics` | `orders_insert ... with check (true)` lets anyone who can insert write an order for any `user_id`. Combined with finding 2, an anonymous visitor holding only your public anon key can create orders on behalf of any user. | | 2 | CRITICAL | `security-privileges` | `grant select, insert, update ... to anon`. Orders belong to signed-in users, so `anon` needs no privilege at all. None of the three policies has a `to` clause, so each applies to every role, including `anon`. | | 3 | HIGH | `security-privileges` | `authenticated` can `update` every column of its own rows: the owner can still run `update orders set status = 'paid', total_cents = 1` from the browser. | | 4 | HIGH | `security-rls-basics` | The insert path also trusts `status`: a client can insert a row that is already `status = 'paid'`. | | 5 | HIGH | `security-rls-performance` | All policies call `auth.uid()` bare, so it is evaluated per row. Wrap it as `(select auth.uid())` so it is evaluated once per statement. | | 6 | HIGH | `schema-foreign-key-indexes`, `query-missing-indexes` | No index on `user_id`. It is the foreign key, the RLS filter column and the column every "my orders" query filters on. Each RLS-filtered select over 1.8M rows is a sequential scan. | Fixing migration (file 1 of 2, excerpt) ```sql set local lock_timeout = '5s'; revoke all on public.orders from anon; revoke update, delete, truncate, references, trigger on public.orders from authenticated; grant select, insert on public.orders to authenticated; drop policy if exists orders_select on public.orders; drop policy if exists orders_insert on public.orders; drop policy if exists orders_update on public.orders; create policy orders_select on public.orders for select to authenticated using ((select auth.uid()) = user_id); create policy orders_insert on public.orders for insert to authenticated with check ((select auth.uid()) = user_id and status = 'pending'); -- No update or delete policy: denied for every role except service_role. ``` File 2 holds only `create index concurrently if not exists orders_user_id_created_at_idx on public.orders (user_id, created_at desc);` because `concurrently` cannot run inside the transaction the Supabase CLI wraps around each migration. [The full answer continues with findings 7–12, the lock plan for 1.8M rows, the status and total_cents check constraints added `not valid` then validated, an `explain analyze` to run before and after, and one RLS test per role.]
Use it to make your coding agent follow Supabase's 31 Postgres rules for RLS, indexes, schema and migrations before it touches your database.
Beat sheet | Beat | Purpose | What the viewer must get | Seconds | |---|---|---|---| | 1 | Hook | A reporter in the rain, recording a source who is trusting him with something | 4 | | 2 | Problem / stakes | The recording is precious and private; the small device in his wet hand is what holds it | 4 | | 3 | Reveal | Later that night, at home, he sets Fieldnote down and starts it working | 4 | | 4 | Proof | The words appear on its own e-ink screen: transcribed on the device, no laptop, no cloud | 8 | | 5 | Payoff + CTA | He has every word, privately; music enters; end line | 4 | Storyboard table (shots 1–3 of 6) | # | Sec (gen → use) | Framing | Camera move | Subject action | Dialogue / on-screen text | Audio / ambience | Continuity notes | |---|---|---|---|---|---|---|---| | 1 | 4 → 4 | Medium shot, eye level, over the source's shoulder (her back soft in frame-left foreground); he stands under the awning facing screen left | Slow handheld push-in | Holds Fieldnote out toward her at chest height; listens; gives one small nod | Source (on camera, from behind): "They said it would stay between us." | Location A bed; her voice close; rain on the awning | Location A. Faces screen left. Recorder in his right hand, red light on. Jacket zipped halfway, wet shoulders. | | 2 | 4 → 4 | Extreme close-up on his right hand holding Fieldnote, the source's coat a soft blur beyond | Static | Rain beads on the graphite body; the red light glows steadily; his thumb shifts once on the edge | VO: "What they tell me stays with me." | Location A bed, close rain ticks on the device | Location A. Recorder points screen left. Screen pale grey and blank. Ends with recorder centred, for a match cut. | | 3 | 4 → 4 | Medium close-up on the kitchen table under the pendant lamp | Static | His right hand enters from frame right and sets Fieldnote down, centred; his thumb presses the round button; hand withdraws | none | Location B bed; soft tap of the recorder on wood; one button click | Location and lighting change (B). Match cut from shot 2 on recorder position. Device dry now, red light off. | [The full answer continues with shots 4–6 and a coverage alternate, the ten-line continuity block pasted word for word into all seven shot prompts, each prompt's first and last frame, and the stitch plan with voice-over script, caption placement and export settings.]
Use it to turn a script or one-paragraph brief into a beat sheet, storyboard table, matching per-shot Sora 2 prompts and a stitch plan.
Settings: model `sora-2` · size `1280x720` · seconds `8` · characters: none Before A cool cinematic shot of a woman drinking coffee in a nice cafe, make it 10 seconds. After Style: Contemporary 35 mm film look, natural and understated, soft contrast, fine grain, warm-neutral grade. A small neighbourhood cafe on a quiet weekday morning. A woman in her early 30s with shoulder-length dark hair tied loosely back, wearing an oatmeal knit sweater, sits alone at a round marble table by a large street-facing window. In front of her: a white ceramic cup of black coffee on a matching saucer and a folded paper napkin. Behind her, out of focus, a wooden counter with an espresso machine and a barista moving between shelves of cups. Thin steam rises from the cup and catches the window light. Cinematography: Camera: medium close-up, eye level, from across the table at a slight angle; slow dolly-in Lens: 50 mm feel; shallow depth of field, sharp on her face and the cup, background soft Lighting: soft morning daylight through the window from camera left as key; warm tungsten pendant glow from the counter as background fill Palette anchors: cream, warm walnut brown, soft white, muted sage, coffee black Mood: calm, quiet, unhurried Actions: - She wraps both hands around the cup and lifts it slowly (first two seconds). - She blows lightly across the surface, then takes one small sip. - She lowers the cup to the saucer and looks out the window with a faint, relaxed smile (final two seconds). Background Sound: Low hiss of the espresso machine, soft clink of cups at the counter, muffled murmur of a few voices; no score. What changed and why - Removed "make it 10 seconds". Duration is an API parameter, not prose, and 10 is not a supported value (4, 8, 12, 16, 20). Set `seconds: 8` in the call; the prompt now fits 8 seconds of action. - "Cool cinematic shot" became a named style. A specific aesthetic instead of a vague adjective, set first so it frames everything else. - "Drinking coffee" became three timed beats. Lift, blow and sip, lower and look out. Beats with timing cues are far more reliable than a single vague verb. [five more changes in the full answer: subject anchors, concrete nouns, one camera move, named light sources, background sound]
Use it to write or rewrite Sora 2 prompts with the right settings, camera, timed action beats, light, dialogue and sound.

Use it to score a pricing page or in-app paywall on eleven criteria and get three ranked fixes with rewritten copy and one experiment to run.
Pack: `references/pack-product-shots.md`. Three options 1. PS-09 Strawberry Milkshake Product and Infographic. A ready-made premium cold-drink hero shot: seamless pastel backdrop, realistic condensation droplets and "premium café advertisement" styling, which is exactly the look a chilled cold-brew can needs. Prompt by @Strength04_X (𝐌), source: https://x.com/Strength04_X/status/2090665058980462697, via awesome-nano-banana-pro-prompts, CC-BY-4.0. 2. PS-02 Luxury Chocolate Ad Still Life. The only prompt in the pack that is a full ad layout with on-image copy, but you would have to delete its placeholder copy and invented logos and rebuild most of the props. Prompt by @AiwithLucas_ (Lucas AI), source: https://x.com/AiwithLucas_/status/2089231817916420518, via awesome-nano-banana-pro-prompts, CC-BY-4.0. 3. PS-01 Premium Beverage Advertising Photography. Written for a beverage-brand hero ad and works from your attached references, but the prompt is only one line long, so lighting and composition would come from what you attach. Prompt by @exywuu (Exywuu), source: https://x.com/exywuu/status/2092222318084788564, via awesome-nano-banana-pro-prompts, CC-BY-4.0. Pick: PS-09 What to attach. Image 1: a clear, front-on photo of your real Northbound can, logo facing the camera. Adapted prompt (4:5 Instagram ad) Image 1: product photo of the Northbound cold-brew coffee can. Use this exact can. A vertical 4:5 Instagram feed ad. A high-quality professional product photograph of a premium cold-brew coffee can, the 330 ml matte navy aluminium can from Image 1 with its white logo, standing upright and centered against a soft pastel pink seamless studio background. Preserve the can's exact shape, proportions, matte navy finish, white logo and label from Image 1; do not redesign it, recolour it or add any new graphics to it. The can sits on the same seamless surface with a few clear ice cubes and a small scatter of dark roasted coffee beans at its base. Realistic condensation droplets cover the can with soft reflections from the studio lights. Elegant soft lighting creates subtle natural shadows beneath the can. The can fills about 55% of the frame height in the lower two thirds of the image, logo facing the camera. Text: the headline "Cold. Bold. Northbound." across the top third of the image, centered, in a bold clean white sans-serif with generous letter spacing, large enough to read on a phone. Render this headline exactly once, spelled exactly as written. Apart from the can's own logo from Image 1, no other text, no prices, no taglines, no extra logos, no watermark. Ultra-sharp focus, DSLR macro beverage photography, premium café advertisement style, hyper realistic, clean commercial composition, 8K. Tool settings: aspect ratio 4:5; in AI Studio or the API also set the output resolution to 2K so the headline stays sharp. Prompt by @Strength04_X (𝐌), source: https://x.com/Strength04_X/status/2090665058980462697, via awesome-nano-banana-pro-prompts, CC-BY-4.0. Adapted: subject swapped from a strawberry milkshake in a glass to the Northbound can, Image 1 reference and preserve-the-product line added, 4:5 aspect ratio and headline added, the original's second (infographic) paragraph removed. [The full answer ends with a one-fix-per-turn table for headline contrast, product drift, garbled text and framing.]
Use it to pick a provenance-checked Nano Banana Pro prompt and adapt it to your product, portrait or poster in one pass.
Assumption: no n8n-MCP server is connected in this session, so I took the skill's fallback path: I wrote the workflow JSON directly from the bundled rules and checked it by review (a rule-check script plus a dry run of every expression). The real validator (`validate_workflow`) was not run, and nothing was deployed.
How the skill routed this
| Step | Sub-skill used | What it decided |
|---|---|---|
| Shape | `n8n-workflow-patterns` | Webhook processing pattern: Webhook, then Validate, then Action, then Respond |
| Node parameters | `n8n-node-configuration` | Slack post needs a channel and text. `responseMode: "responseNode"`. `options.responseCode` set on every Respond node. UUID node ids, no placeholder credentials |
| Expressions | `n8n-expression-syntax` | Webhook data lives under `.body`. Use `$('Webhook').item.json.body.*` once past the IF node. `=` prefix on every expression field |
| Error paths | `n8n-error-handling` | Slack gets `onError: "continueErrorOutput"` and its `main[1]` is wired to the 500 responder. `retryOnFail` is on. Bad input is caught upstream and gets a 400 |
| Validation | `n8n-validation-expert` | Runtime-profile checks done by review. Security warnings get fixed (header auth on the webhook) |
```
Webhook ──> Validate Input ──true──> Post to #signups ──success (main[0])──> Respond 200 OK
└──false──> Respond 400 Invalid Input
Post to #signups ──error (main[1])────> Respond 500 Slack Failed
```
Validation result (excerpt)
```
PASS Webhook responseMode is responseNode (Respond nodes control the reply)
PASS Every branch (success and each error) ends at a Respond to Webhook node: no hanging branch
PASS 'Post to #signups': onError=continueErrorOutput AND main[1] wired
PASS 'Post to #signups': retryOnFail with maxTries <= 5 and waitBetweenTries <= 5000
PASS Every {{ }} field starts with = (no 'Expression requires = prefix' error)
PASS Webhook fields read from .body, never $json.name / $json.email / $json.plan
…
43 passed, 0 failed
```
Expression dry run against sample webhook items:
```
valid IF -> true (Slack) Slack text: New signup: Ana Lima (ana@example.com) on pro
missing_plan IF -> false (400)
bad_email IF -> false (400)
no_body IF -> false (400)
500 body: {"error":"upstream_error","message":"The signup was received but could not be posted to Slack. Please retry later."}
```
Not verified without the server: the node typeVersions (Webhook 2.2, IF 2.3, Slack 2.3, Respond 1.5) and the Slack v2 parameter names; confirm by opening the nodes after import.
[The full answer also holds the six-node list, the importable workflow JSON and the expressions table with the reason behind each one.]Use it to have your coding agent build, configure and validate n8n workflows without the usual expression and node-config mistakes.

Use it to turn an image brief into a GPT Image prompt, with exact on-image text and the right size and quality settings.
2. Tool design
| Tool | Does | Inputs (constraints) | Returns | Read-only / destructive | Errors the model can fix |
|---|---|---|---|---|---|
| `find_customers` | Customers whose name or email contains the text, case-insensitive | `query` string 1–100 (trimmed), `limit` int 1–50 = 20, `cursor` optional | id, name, email; `next_cursor` | read-only, idempotent | no match: try a shorter part of the name or the email domain |
| `list_customer_orders` | One customer's orders, newest first | `customer_id` int ≥ 1, `status` enum optional, `limit`, `cursor` | customer + orders; `next_cursor` | read-only, idempotent | unknown customer: call `find_customers` |
| `get_order` | One order with the customer's name and email | `order_id` int ≥ 1 | order + customer (structured) | read-only, idempotent | not found: check the number or list the customer's orders |
| `refund_order` | Sets a paid order to refunded | `order_id` int ≥ 1, `confirm` int optional, must equal `order_id` | updated order, `previous_status: "paid"` | destructive, idempotent | no `confirm`: preview, ask the user; order pending or already refunded; not found |
Checklist notes: one rule for empty results (an `isError` result with a next step), applied to every tool. All SQL is parameterized. Read tools use a read-only SQLite handle; only `refund_order` opens a writable one, and its `UPDATE ... WHERE id = ? AND status = 'paid'` is a single statement. The guard on the destructive tool is server-side (the preview/confirm step), not just the `destructiveHint` annotation.
3. Scaffold
From `templates/typescript-server.md`. Installed: `@modelcontextprotocol/server` 2.4.0, `@modelcontextprotocol/client` 2.4.0, `zod` 4.6.7; Node 24.6.0.
```
build-an-mcp-server/
data/seed.sql, data/shop.db 3 customers, 5 orders
src/index.ts stdio entry point
src/server.ts createServer(): the four tools
src/shop.ts SQLite client module (the system being wrapped)
test/smoke.ts spawns the server over stdio, calls every tool
```
5. Test (Inspector CLI, eval loop layer 2)
```
$ npx @modelcontextprotocol/inspector --cli npx tsx src/index.ts --method tools/call --tool-name get_order --tool-arg order_id=1001
{
"content": [{ "type": "text", "text": "Order #1001 for Ada Lovelace <ada@example.com>: $49.99, paid, created 2026-10-02T09:15:00Z" }],
"structuredContent": { "order": { "id": 1001, "customer_id": 1, "total_cents": 4999, "status": "paid", "created_at": "2026-10-02T09:15:00Z", "customer_name": "Ada Lovelace", "customer_email": "ada@example.com" } }
}
```
Built, tested, not tested
- Built: four tools over `data/shop.db`, server-side preview/confirm guard on `refund_order`, smoke test, configs for Claude Code, Codex and Cursor, README.
- Tested: starter smoke (pass), full smoke test 14 checks (pass), strict typecheck (pass), Inspector CLI call (pass), seed database unchanged after tests.
- Not tested: the Inspector web UI by hand and the model check (running the five real requests through a live Claude Code session and recording which tool it picks).
[The full answer also holds `src/shop.ts`, `src/server.ts`, `test/smoke.ts` and the three client config snippets.]Use it to design, build, test and ship an MCP server your agents use well: 3 to 7 well-designed tools, a tested starter and ready client configs.
Phase 1: Root cause investigation
Full suite: FAIL test_expired_coupon_rejected_twice (tests/test_checkout.py:16) AssertionError: Coupon(code='OLD50', …expires_at=2020-01-01…) is not None.
In isolation the same test fails at a different line (17): AssertionError: 50.0 != 100.0. So the first lookup in a process is rejected and every later lookup is accepted; in the full suite the first lookup was done by test_expired_coupon_rejected. Cross-test pollution through module-level state.
Cache probe at the boundary: cache before [] → 1st None → cache after ['OLD50'] → 2nd Coupon(OLD50). Docstring says the cache was added in 3f2a1c9 "to cut DB reads": that is the regression point.
Trace (root-cause-tracing.md): apply_coupon → get_coupon → `if code in _CACHE: return _CACHE[code]`. get_coupon writes the coupon to _CACHE before the expiry check, and the cache-hit branch has no expiry check at all.
Phase 2: Pattern analysis
Working path: cache miss → _load → expiry check. Broken path: cache hit. Expiry depends on `now`, passed per call, so it cannot be a cached property; "don't cache expired coupons" alone would still serve a coupon cached while valid after it expires.
Phase 3: Hypothesis
"get_coupon returns cached coupons without re-checking expires_at against the caller's now; the cache was added around the lookup but the validity check stayed only on the miss path." Confirmed by the probe above.
Phase 4: Implementation
Failing test first: test_cached_coupon_rechecked_for_expiry (look up while valid, then after expiry) plus a setUp that clears the module cache. Before the fix: 5 tests, 2 failures, both for the expected reason.
Fix at the source: the cache stores the raw record; the expiry check runs on every lookup, cached or not.
c = _CACHE.get(code)
if c is None:
c = _load(code)
if c is None:
return None
_CACHE[code] = c
if c.expires_at < now:
return None
return c
Verification: python3 -m unittest -v → Ran 5 tests … OK. The originally failing test in isolation, 3 runs: OK, OK, OK. Fix attempts: 1.Stops your coding agent from guessing: root cause first, one hypothesis at a time, failing test before the fix.

Idea to approved design to test-first plan to working code, with an approval gate between each stage.
Scope: app/dashboard/page.tsx and the modules it imports (components/charts, UsageCard, ProjectList, RichTextEditor, lib/api.ts).
| # | Rule | File:line | Impact | Finding |
|---|---|---|---|---|
| 1 | async-dependencies | app/dashboard/page.tsx:8-10 | CRITICAL | getProjects(user.id) and getUsage(user.id) depend only on `user` but are awaited one after the other: 3 sequential round trips before any HTML is sent, where 2 would do. Promise.all them. |
| 2 | bundle-barrel-imports | components/ProjectList.tsx:3 | CRITICAL | `import _ from 'lodash'` in a 'use client' file ships all of lodash for one sortBy. Use lodash/sortBy, or toSorted / localeCompare. |
| 3 | async-suspense-boundaries | app/dashboard/page.tsx:7-19 | HIGH | The whole page, including the static shell and RichTextEditor, waits for all three fetches. Move usage and projects into async children in <Suspense> so the shell streams first. |
| 4 | server-serialization | app/dashboard/page.tsx:16 | HIGH | The raw projects array goes to client ProjectList, which reads only id, name and status. Map to {id, name, status} on the server. |
| 5 | rerender-no-inline-components | components/ProjectList.tsx:7 | HIGH | `Row` is defined inside ProjectList, so every keystroke in the filter remounts every <li>. Interaction cost, not first load. Hoist Row. |
| 6 | rerender-derived-state-no-effect | components/UsageCard.tsx:4-5 | MEDIUM | percent is kept in state and set in useEffect: server HTML renders "0% of plan used" and the real value appears only after hydration. Compute it during render. |
Score: 62 / 100 (100 − 2×10 CRITICAL − 3×5 HIGH − 1×3 MEDIUM). This counts rule findings; it is not a measured load time.
Checked and not scored: the local charts barrel (five tiny stubs, tree-shakeable); RichTextEditor is a <textarea> today, so no dynamic import yet. Not a performance rule but it blocks the build: components/charts/index.ts re-exports names the chart files do not export, and page.tsx reads `params.id` synchronously though params is a Promise in Next 15.
What makes the first load slow, in order:
1. The server waterfall (rows 1 and 3). Parallelizing projects and usage removes one full round trip; Suspense lets the header paint after only getUser.
2. Client JS weight (row 2). A one-line import change.
3. Payload and first-paint correctness (rows 4 and 6).
Row 5 is an interaction cost; fix it after the others. Fixes were not requested, so no files were changed.70 prioritized performance rules for React and Next.js that your agent applies while coding and scores in review.

Audit a live page that isn't converting: a 7-point scorecard, quick wins, test ideas and before/after copy.

24 named design moves for your coding agent: bolder, quieter, typeset, distill, polish, plus a heuristic critique and a technical audit.
Showing 24 of 62