diff --git a/STATUS.html b/STATUS.html index 6d888e1..2b219f8 100644 --- a/STATUS.html +++ b/STATUS.html @@ -14,7 +14,7 @@ const STATUS = { app: "TidyDisk", version: "1.0.0", - updated: "2026-07-16", + updated: "2026-07-17", phase: "🚀 v1.0.0 — TidyDisk is LIVE-ready. All three PRs merged to main (landing refresh #22, license gate + analytics #30, rename + honest copy #31); the release gate is lifted; tidydisk.app serves the new site with the €19 Polar checkout wired. The v1.0.0 tag fires the signed-release pipeline (first tidydisk-arm64.dmg). Your last clicks before selling: publish the draft release when the workflow finishes, then run the launch playbook (#27 scan screenshot, #28 assets, #29 the wave).", summary: "This session (2026-07-16): the menu bar panel is no longer a mini node_modules cleaner. It is a read-only dashboard: an aggregate 'Tracked on disk' hero (every tracked byte counted once, against the sum of your three limits) plus four click-through rows (Projects / Caches / Packages / Docker) that open that tab in the main window. Gone from the panel: the reclaimable list, the Clean stale CTA, the pnpm prune row, and the paywall. NOTE: that removes all three paywall_shown triggers from the panel, so the launcher is now the only paid-conversion surface. Built spec -> plan -> subagent-driven TDD (9 tasks, fresh implementer + independent review each); all arithmetic + gating lives in pure panelAreas.ts / launcherNav.ts so the .tsx stays thin. The hero counts the pnpm store once and is therefore smaller than the four rows added up (rows mirror their tabs so click-through never lies); Docker refreshes on open when stale, but the store re-sizes only on a scan (no du on every popover open), and the panel never computes the package inventory. 315 tests, typecheck/lint/build green. In your court: manual QA (needs a running app + Docker daemon) and whether the launcher needs its own paywall moment now the panel has none. SHIPPED: merged to main via PR #42 (the whole Docker stack + the panel; PR #40 auto-closed) and tagged v1.1.0, which fired the signed-release pipeline to build a DRAFT dmg. Test that draft, then publish it to promote v1.1.0 to latest. Prior session (2026-07-14): finished the Docker feature on feat/docker-project-grouping (spec + plan docs/superpowers/2026-07-14-docker-buildcache-*, subagent-driven TDD, 6 tasks + a final-review fix wave). Two things you asked for. (1) Docker build cache no longer sits in the Docker tab: groupDockerForDisplay drops the build-cache group (items still count in the Docker header gauge, but the list only shows project/repo/orphan resources), and it now appears as a single aggregated 'Docker build cache' row in the CACHES tab next to the pnpm store. Its red Delete routes through the EXISTING requestDockerPrune('buildCache') — same license gate, same confirm, same toast, no new delete path — and the Caches tab was generalized from a hard-coded pnpm row into a list of 'live caches' (keyboard nav + Enter across them; the tab also auto-scans Docker on open when data is missing/stale, mirroring the Docker tab). (2) The deletion buttons now unmistakably read as deletions: the Docker bulk-prune buttons became 'Delete unused images / dangling images / stopped containers / unused volumes' with a trash icon and red danger styling; the build-cache row's Delete is likewise danger-styled (CacheRow gained an opt-in danger + icon variant); the per-item unused-image/volume/container trash stays hover-revealed like the node_modules rows (already danger). SAFETY UNCHANGED: presentation + a delete-button relabel; the typed-name volume confirm gate is byte-identical and build cache is still never individually removable (3-layer block, verified). Whole-branch Opus review: 1 Important fixed — Caches Enter indexed the full cache list while the rows were query-filtered, so Enter could fire a row scrolled off by search; a new tested selectedActionableCache() guard makes Enter a no-op unless the selected row is visible + actionable (confirm-gated the whole time, never a silent delete). Also swept em dashes out of the npm/yarn/bun placeholder copy. 293 tests, typecheck/lint/build green. In your court: manual QA (needs a running app + Docker daemon) and push/PR (rides PR #42 or its own branch, your call). Prior session (2026-07-09): per-tab headline data-viz + Docker auto-scan, BUILT on feat/docker-project-grouping (spec + plan in docs/superpowers/2026-07-09-*, subagent-driven TDD, 8 tasks + a final-review fix). Each launcher tab's header slot and footer line now reflect THAT tab's core metric instead of always node_modules: Projects/Caches/Docker each fill a size-vs-limit Gauge against their OWN configurable GB limit (new cacheThresholdGB=10, dockerThresholdGB=20 in Settings > Scanning > Storage limits, clamped 0.1-1000 like the node_modules threshold); Packages shows a compact SeverityMeter (a stacked critical/high/moderate/low advisory bar + outdated count, full breakdown in the tooltip). A new TabHeadline switches the header slot; a new pure tabSummary() switches the footer line; both null-gate honestly (no gauge when a store/daemon is absent). The Docker tab auto-scans on open when its data is missing or >5 min stale (loop-guarded, decoupled from the disk rescan; manual Cmd-R/refresh kept). Whole-branch Opus review returned one Important finding, fixed: the Packages headline no longer shows a false 'all clear' when advisories were never actually checked (checks off / offline / before enrichment now render nothing) - the same fix removes the first-open flash. 277 tests, typecheck/lint/build green; Projects tab confirmed unregressed and the Docker typed-name confirm safety gate untouched. In your court: manual QA (needs a running app + Docker daemon) and whether this rides the still-pending Docker-grouping PR #42 or gets its own branch/PR. Prior sessions: Two new app features shipped on feat/scan-coverage-and-liveness (PR #39), each brainstormed → spec'd → planned → built via subagent-driven TDD (15 tasks, fresh implementer + independent review each), then a whole-branch review on the strongest tier (READY TO MERGE). (1) External-drive / scan-location coverage — the scanner was hard-wired to ~; now a Settings 'Scan locations' section lists mounted external volumes (detected under /Volumes, boot disk excluded by device id) as toggles plus an 'Add folder…' picker, choices persist in a new scanRoots setting, and resolveScanRoots feeds home + opted-in roots to the scanner each scan (mounted-only, deduped, nesting-dropped) — so node_modules on an external SSD or any folder is finally scannable and cleanable; an unplugged drive's projects just drop off next scan and return on replug, and a delete-time guard refuses trashing a path that vanished. (2) Live/running indicator — a green dot marks any project with a process running from inside it (one lsof cwd sweep per refresh, best-effort port label, soft-fails to no-dots on any error), its delete is disabled and main hard-refuses it re-checked at the instant of delete, so you cannot trash a running app's node_modules; polls every 45s while a window is open. Shared: unified deleteNodeModules → { freed, blocked? }; a batch deleteMany IPC keeps 'Clean N stale' to one liveness check (the final review's one Important finding). 183 tests, typecheck/biome/build green; the real lsof flags smoke-tested on macOS. Awaiting your review/merge + manual pass (needs a physical external drive + a running dev server). Previous session context: SEO + content session (site only, no app code). Audited tidydisk.app's SEO: robots.txt and sitemap.xml were 404, no canonical tags, no structured data, and the apex→www redirect direction contradicts the code's canonical base (fix is one Vercel dashboard click on your side). Built the fixes plus a self-publishing blog on feat/blog-seo (PR #34): markdown articles in site/content/blog/ parsed by a tested lib/blog pipeline; every surface (listing, article pages, sitemap, RSS) revalidates hourly and filters on publish date, so the 10 pre-written keyword-targeted articles release themselves one per week (2026-07-04 → 2026-09-05) with zero deploys. Also: SoftwareApplication + BlogPosting JSON-LD, RSS feed, Blog in nav/footer. 17 unit tests, build green, rendering verified via Playwright. Earlier context: decided to reposition the product as TidyDisk (from 'Clean my node_modules'), broadening the pitch from node_modules to all developer disk waste (caches, builds, later Docker/Xcode), and to monetize via free-scan / paid-clean rather than a free/OSS-only model: the free scan stays the marketing engine (its shocking number is the shareable artifact) and the one-click Clean action becomes a paid, lifetime unlock (€19 founding → €29). Produced two artifacts: TIDYDISK-LAUNCH.html — an interactive, on-brand launch dashboard (live price→sales calculator, pricing ladder, offer stack, competitor table, Show HN / Reddit drafts, and a persistent 2–4 week launch checklist; rendering + interactivity verified via Playwright) — and docs/superpowers/specs/2026-07-01-tidydisk-gtm-and-monetization-design.md, the GTM + monetization spec, whose one implementation slice is a license gate on the destructive actions (new src/main/license/*, offline-verifiable keys, enforced inside the destructive IPC handlers, Merchant-of-Record checkout via Lemon Squeezy/Paddle). Nothing renamed or gated in code yet — awaiting your review of the spec and your go-ahead. Still open from the prior session: publish the v0.2.0 draft release and deploy site/ to Vercel.", @@ -135,6 +135,7 @@ /* Things ONLY the user can do. Claude removes/updates these as they get done. */ userActions: [ + { id: "proofread-landing-repositioning", category: "review", label: "Native-speaker proofread of the new landing copy (fr/es/de/pt)", detail: "The landing-page repositioning ('all developer disk waste') added new fr/es/de/pt copy: the widened hero subhead + statement band, the four-area section, the Docker feature row, and the broadened meta description. It is high-quality machine translation, structurally validated (typecheck enforces the Dictionary shape across all 5 locales, JSX structure preserved, no em dashes, technical tokens kept), but not human-proofread. Before leaning on the non-English pages for marketing, have a native speaker skim each locale's home for tone/idiom. Copy lives in site/lib/i18n/dictionaries/.tsx." }, { id: "release-110-apple-agreement", category: "external", label: "Accept the pending Apple agreement, then re-run the v1.1.0 release", detail: "BLOCKED: the v1.1.0 release build ran on the tag and got through typecheck/tests/build AND code-signing, but Apple notarization failed with HTTP 403: 'A required agreement is missing or has expired.' Apple periodically publishes updated legal agreements and blocks notarization until the account holder accepts. Fix (only you can): sign in to developer.apple.com/account, look for a 'Review Agreement' / pending-agreement banner (Membership + Agreements, Tax, and Banking), accept it. Then re-run the release: either re-run the failed 'Release' workflow run on GitHub Actions, or ask me to re-run it (gh run rerun) — the v1.1.0 tag already exists, so no new tag is needed. Once notarization passes it publishes a DRAFT dmg; test it, then publish to promote v1.1.0 to latest." }, { id: "qa-panel-dashboard", category: "testing", label: "Manual pass of the new menu bar panel", detail: "Needs a running app + a Docker daemon (CI has neither). Open the popover: confirm the 'Tracked on disk' hero and the four rows (Projects / Caches / Packages / Docker), and that clicking each row opens the main window on THAT tab (Cmd-, still opens Settings). Confirm the popover opens instantly with a cold Docker cache (numbers fill in a moment later, they never block the open). Turn Docker off in Settings: its row disappears, its bytes leave the hero, and the hero reads 'across 2 areas'. Before ever visiting the Packages tab, its row should read 'Not checked yet' rather than claiming 'all clear', and clicking it should compute the inventory there. Finally, delete a project's node_modules in the launcher and confirm the panel's Projects row and hero both move." }, { id: "paywall-surface-decision", category: "vision", label: "Decide where the paywall lives now the panel has none", detail: "The panel rework made the popover read-only, which removed all three paywall_shown triggers it used to fire (clean_stale, prune, affordance). The launcher is now the only paid-conversion surface in the app. Decide whether that is fine or whether the launcher needs a stronger paywall moment before this ships; the spec deliberately ruled this out of scope rather than guessing." }, @@ -233,7 +234,8 @@ { date: "2026-07-12", text: "Added the legal pages + fixed the reviewed GDPR consent-withdrawal gap (feat/site-analytics-consent, PR #44). You flagged (correct) that once a visitor chose in the cookie banner there was no way to change or withdraw consent, violating GDPR Art. 7(3). Fixed: a footer 'Cookie preferences' control dispatches a window event that reopens the banner, and declining now also clears any GA cookies (best-effort _ga/_gid removal). Then built /privacy and /legal across all 5 locales from your details (Ahmed ABOUELLEIL SAYED, 26 rue Jean Voillot 69500 Bron France, contact@tidydisk.app, SIRET 90884903700027): /privacy documents GA (consent-based), the app's anonymous PostHog telemetry with its Settings opt-out, Polar as merchant of record, and Vercel hosting, plus legal bases, retention, international transfers via SCCs, and the full GDPR rights list incl. lodging a complaint with the CNIL; /legal is the EU imprint with the identity block, MoR/refunds, French governing law + the EU ODR link, and Vercel's host address. Architecture: factual identity in one untranslated constants file injected by the page components (so translators never touch the name/SIRET/address), long-form prose in a separate typed lib/legal module, English authored by me then fr/es/de/pt produced by 4 parallel translation agents (protected tokens preserved, GDPR->RGPD/DSGVO, no em dashes). Wired into a new footer Legal column, the sitemap (/privacy + /legal x5 locales with hreflang), and a privacy-policy link in the cookie banner. Verified against a production build in a real browser: the EN Legal Notice + FR privacy pages render correctly with the imprint intact, and the withdrawal flow reopens the banner after a prior accept. typecheck + build green (12 new routes). Pushed to PR #44. Your court: optional professional skim of the copy (userActions legal-review) and confirming GA Realtime once deployed (verify-ga-live)." }, { date: "2026-07-14", text: "Finished the Docker feature from your two asks: build caches shouldn't live in the Docker tab, and the delete buttons should look like deletes. Brainstormed the four decisions (single aggregated 'Docker build cache' row in the Caches tab reusing the existing prune confirm/gate; build cache stays counted in the Docker gauge since it's real Docker disk; verb+trash+danger relabel of the bulk-prune buttons; keep the per-item hover trash), spec'd + planned, then executed subagent-driven with TDD (6 tasks: cheap models for the transcription tasks, a standard tier for the CachesView rewrite and the LauncherApp integration, opus for the reviews). Result: groupDockerForDisplay drops the build-cache group; the Caches tab became a generalized 'live caches' list (pnpm + Docker build cache, keyboard-navigable, auto-scans Docker on open); Docker bulk buttons now say 'Delete unused images' etc. in red with a trash icon; CacheRow gained an opt-in danger+icon action variant. SAFETY stayed frozen — the build-cache Delete routes through the unchanged requestDockerPrune('buildCache'), the typed-name volume gate is byte-identical, and build cache is still un-deletable individually (canRemove + removeDockerItem + coerceRemoveArgs all block it). Two per-task reviews flagged missed lint in the TDD-only tasks (biome format) which I fixed. The whole-branch Opus review earned its keep: it caught that Caches Enter indexed the full cache list while the rows were query-filtered, so pressing Enter with an active search could trigger a row scrolled off screen (confirm-gated, so never a silent delete) — fixed with a tested selectedActionableCache() guard, plus a footer-hint gate and an em-dash sweep of the placeholder copy. 293 tests, typecheck/lint/build green. Not pushed — your call whether it rides PR #42 or its own branch; manual daemon QA still yours." }, { date: "2026-07-16", text: "Menu bar panel became a read-only dashboard: aggregate 'Tracked on disk' hero + four click-through rows (Projects / Caches / Packages / Docker) that open that tab in the main window; the panel's reclaimable list, Clean-stale CTA, pnpm prune row and paywall are all gone (so the launcher is now the only paid-conversion surface). Brainstormed with a visual companion (picked the aggregate-hero layout), spec'd the honest-number decisions (hero counts the pnpm store once so it is deliberately smaller than the four rows summed, since rows mirror their tabs and click-through must never lie; 'tracked' not 'reclaimable' because the store's prunable share is unknown until pruned; combined limit = sum of the three existing limits), then built subagent-driven with TDD across 9 tasks. All arithmetic + gating went into pure panelAreas.ts (hero + row gating) and launcherNav.ts (nav-target map), keeping the .tsx thin, per the pure-logic-only test setup. Freshness tuned to not regress popover open speed: Docker stale-refreshes on open (5 min, non-looping), the pnpm store re-sizes only when a scan finishes (getPnpmStoreInfo has no TTL, so a timer would du on every open), and the panel never computes the package inventory (cached read only, placeholder row clicks through to compute it in the tab). One plan-authored test bug caught + fixed (asserted 41.2 GB but the code sums 34.8+6.4, off at the IEEE 754 bit). 315 tests, typecheck/lint/build green." }, - { date: "2026-07-16", text: "Shipped the panel + the whole Docker stack to main and cut v1.1.0. You confirmed the panel looked right (after one fix: the per-row PixelMeter bars were collapsing to zero width inside AreaBar's flex column, since the meter cells are flex:1 with no basis and the root had no width, so only the GB number showed; fixed with width:100% on the PixelMeter root, a no-op in the block-context hero). Then, on your go-ahead: synced main into the branch and resolved the one STATUS.html conflict by taking the union (main's #43/#44 GA + legal work kept alongside the panel session), bumped to v1.1.0, retargeted PR #42's base from feat/docker-cleanup to main and merged it (bringing the entire Docker stack; PR #40 auto-closed), waited for CI green (check + package-macos), then tagged v1.1.0 which fired the signed-release pipeline. The build passed typecheck/tests/build and CODE-SIGNED fine, but Apple NOTARIZATION failed with HTTP 403 'a required agreement is missing or has expired' — an Apple Developer account agreement you need to accept, not a code/secrets problem (nothing published; v1.0.0 is still latest). Your court: accept the pending agreement at developer.apple.com/account then re-run the release (tag already exists), plus the still-pending manual daemon QA + the launcher-paywall decision. Tracked in userActions release-110-apple-agreement." } + { date: "2026-07-16", text: "Shipped the panel + the whole Docker stack to main and cut v1.1.0. You confirmed the panel looked right (after one fix: the per-row PixelMeter bars were collapsing to zero width inside AreaBar's flex column, since the meter cells are flex:1 with no basis and the root had no width, so only the GB number showed; fixed with width:100% on the PixelMeter root, a no-op in the block-context hero). Then, on your go-ahead: synced main into the branch and resolved the one STATUS.html conflict by taking the union (main's #43/#44 GA + legal work kept alongside the panel session), bumped to v1.1.0, retargeted PR #42's base from feat/docker-cleanup to main and merged it (bringing the entire Docker stack; PR #40 auto-closed), waited for CI green (check + package-macos), then tagged v1.1.0 which fired the signed-release pipeline. The build passed typecheck/tests/build and CODE-SIGNED fine, but Apple NOTARIZATION failed with HTTP 403 'a required agreement is missing or has expired' — an Apple Developer account agreement you need to accept, not a code/secrets problem (nothing published; v1.0.0 is still latest). Your court: accept the pending agreement at developer.apple.com/account then re-run the release (tag already exists), plus the still-pending manual daemon QA + the launcher-paywall decision. Tracked in userActions release-110-apple-agreement." }, + { date: "2026-07-17", text: "Repositioned the landing page from 'node_modules cleaner' to 'all developer disk waste' (feat/landing-all-disk-waste, site only). Brainstormed with a visual companion (picked a four-peer-cards 'areas' section over an aggregate bar or a tabbed app-window mock), spec'd, planned, then built subagent-driven with TDD across 7 tasks in all 5 locales. Changes: hero subhead + the 'heaviest object' band widened to name Docker/caches/dead-projects (joke kept); a new Areas section (Projects / Caches / Packages / Docker peer cards, Docker accented) between the band and the features; Docker added as a 5th feature row with a hand-built by-project visual + a new i-docker sprite icon; the 'coming soon' pill reframed to only genuinely-future items; meta.description broadened; and the hero mockup rebuilt to show the app's NEW panel dashboard (the old one recreated a reclaimable-list UI the app no longer has). The Dictionary type forced all 5 locales (features became a 5-tuple, a new areas slice), so fr/es/de/pt were translated in the same pass (per-locale tab-title choices matched each dictionary's existing naming). Reviews caught + fixed one whole-branch issue the per-task gates missed: 6 French no-unescaped-entities lint errors (straight apostrophes in JSX text where the site convention is curly) fixed by currying them. typecheck + build green (41 pages, 5 locales); site lint clean except a pre-existing CookieConsent error from #44 (not this branch, not in CI). Not pushed; your court: review/merge the PR + an optional native-speaker proofread of the new machine-translated fr/es/de/pt copy." } ] }; diff --git a/docs/superpowers/plans/2026-07-16-landing-page-all-disk-waste.md b/docs/superpowers/plans/2026-07-16-landing-page-all-disk-waste.md new file mode 100644 index 0000000..a000ba8 --- /dev/null +++ b/docs/superpowers/plans/2026-07-16-landing-page-all-disk-waste.md @@ -0,0 +1,702 @@ +# Landing Page "All Developer Disk Waste" Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Reposition the TidyDisk landing page from a node_modules cleaner to "all developer disk waste" (projects + caches + packages + Docker), across all 5 locales. + +**Architecture:** The site (`site/`) is Next.js + Tailwind v4, fully component-assembled and dictionary-driven. All copy lives in `lib/i18n/dictionaries/{en,fr,es,de,pt}.tsx` under the `Dictionary` contract in `lib/i18n/i18n.types.ts`; components are presentational and read `dict` slices. All product visuals are hand-built inline TSX/SVG (no image assets). Work: broaden existing copy, add a new "Areas" section + dict slice, add Docker as a 5th feature row (widening the `features` tuple), rebuild the hero mockup to the new panel dashboard, and translate everything. + +**Tech Stack:** Next.js (static export), React 18, Tailwind v4, TypeScript. + +**Spec:** `docs/superpowers/specs/2026-07-16-landing-page-all-disk-waste-design.md` — read it before Task 1. Decisions referenced as D1-D9. + +## Global Constraints + +- **Work only in `site/`.** No app code. Run all commands from `site/`. +- **Package manager: pnpm.** `pnpm typecheck` (`tsc --noEmit`), `pnpm build` (`next build`), `pnpm lint` (`eslint`). +- **The `Dictionary` type is the gate.** Adding a required field or widening a tuple in `lib/i18n/i18n.types.ts` will not compile until **all five** dictionaries implement it. A green `pnpm typecheck` proves no locale was left behind. +- **`ReactNode` copy fields must reproduce the SAME JSX structure across all 5 locales** — identical tags and classNames (``, ``, ``, the accent ``). Only the human-readable text is translated. +- **Do NOT translate technical tokens:** `node_modules`, `rm -rf`, `pnpm`, `npm install`, `.next`, `dist`, paths, and brand names (`Docker`, `npm`, `yarn`, `bun`). Prices read `€19` / "19 euros" as in English. +- **No em dashes in any language.** Use commas, colons, or full stops. +- **One folder per component:** `Component.tsx`, `Component.types.ts`, `index.ts`, optionally `.constants.ts`. +- **English is the source of truth** (`en.tsx`); author en first, then translate to fr/es/de/pt matching the tone of the existing dictionary copy. +- **No new image assets** — all visuals inline TSX/SVG. +- Conventional commit subjects: `feat(site): …`, `fix(site): …`, `chore(site): …`. + +--- + +## File Structure + +**Create:** +- `site/components/Areas/Areas.tsx`, `Areas.types.ts`, `Areas.constants.ts`, `index.ts` — the four-peer-cards section (Task 5) +- `site/components/Features/DockerVisual.tsx` — the hand-built Docker-by-project mockup (Task 2) +- `site/components/Hero/HeroPanel.tsx` — the new dashboard mock for the hero (Task 6) + +**Modify:** +- `site/components/SvgSprite/SvgSprite.tsx` — add an `i-docker` symbol (Task 2) +- `site/lib/i18n/i18n.types.ts` — widen `features` to a 5-tuple; add `AreaCardCopy`/`AreasCopy` + `areas` field (Tasks 3, 4) +- `site/lib/i18n/dictionaries/{en,fr,es,de,pt}.tsx` — broadened + new copy (Tasks 1, 3, 4) +- `site/components/Features/Features.tsx` — render the 5th (Docker) row (Task 3) +- `site/components/pages/HomePage/HomePage.tsx` — render `` between band and features (Task 5) +- `site/components/Hero/HeroScene.tsx` — swap `` for `` (Task 6) + +**Left unchanged:** `AppPanel` (still used by `app/og/page.tsx`), `WhyLifecycle`, `HowItWorks`, `Download`, `FinalCta`, `Footer`, `Navbar`, blog. + +--- + +### Task 1: Broaden existing copy (all 5 locales) + +Copy-only edits to existing dictionary fields. No type change, so typecheck stays green throughout. Covers D2 (band), D3 (hero subhead), D6 (coming-soon pill), D9 (meta description). + +**Files:** +- Modify: `site/lib/i18n/dictionaries/en.tsx` then `fr.tsx`, `es.tsx`, `de.tsx`, `pt.tsx` + +**Interfaces:** +- Consumes: nothing. +- Produces: no signature change (edits existing fields `hero.body`, `band.statement`, `grid.comingSoonText`, `meta.description`). + +- [ ] **Step 1: Rewrite the four English fields in `en.tsx`** + +`hero.body` (D3 — keep the trailing `rm -rf`): +```tsx + body: ( + <> + Dev work quietly eats your Mac: heavy node_modules, Docker + images, build caches, forgotten experiments. TidyDisk watches from the + menu bar and gives the space back in one click. Safely, to the Trash, + never rm -rf. + + ), +``` + +`band.statement` (D2 — keep the joke, widen the punchline, keep `` + accent ``): +```tsx + statement: ( + <> + node_modules is the heaviest object in the known universe. + But it is not alone: Docker images, build caches and dead projects pile + up too. TidyDisk clears all of it. + + ), +``` + +`grid.comingSoonText` (D6 — frame explicitly as still-future so it never implies shipped things are unshipped): +```tsx + comingSoonText: ( + <> + Next up: npm, yarn & bun caches, plus per-project build outputs like{" "} + .next and dist. + + ), +``` + +`meta.description` (D9 — add Docker + caches to the enumeration, plain string): +```tsx + description: + "TidyDisk lives in your macOS menu bar and shows what your dev projects really cost: every node_modules folder, your pnpm store and package-manager caches, Docker images and volumes, and every installed package. Free to scan. One-click cleanup with a 19 euro lifetime license. Safely, to the Trash, never rm -rf.", +``` + +- [ ] **Step 2: Verify English compiles and renders** + +Run: `cd site && pnpm typecheck` +Expected: PASS (no type change; edited fields keep their shapes). + +- [ ] **Step 3: Translate the four fields into `fr.tsx`, `es.tsx`, `de.tsx`, `pt.tsx`** + +For each of the four locale files, replace the same four fields with a translation of the English above. Rules (binding): +- Keep the **exact JSX structure**: `hero.body` keeps its `node_modules` and trailing `rm -rf`; `band.statement` keeps `node_modules` and the accent `…`; `comingSoonText` keeps `.next` and `dist`. +- Do **not** translate `node_modules`, `rm -rf`, `Docker`, `npm`, `yarn`, `bun`, `.next`, `dist`. Keep `19 euros` as the price wording (matching each locale's existing `download`/`meta` copy). +- **No em dashes.** Match the tone and register of the surrounding copy already in that dictionary. + +- [ ] **Step 4: Verify all locales compile and build** + +Run: `cd site && pnpm typecheck && pnpm build` +Expected: both PASS; static export of `/`, `/fr`, `/es`, `/de`, `/pt` succeeds. + +- [ ] **Step 5: Em-dash sweep of the changed fields** + +Run: `cd site && grep -n "—" lib/i18n/dictionaries/*.tsx` +Expected: no matches introduced by this task (report any pre-existing ones but do not touch unrelated lines). + +- [ ] **Step 6: Commit** + +```bash +git add site/lib/i18n/dictionaries +git commit -m "feat(site): broaden hero, band, pill and meta copy beyond node_modules (5 locales)" +``` + +--- + +### Task 2: Docker icon + DockerVisual component + +Build the standalone Docker feature-row visual and the sprite icon it uses. Not yet wired into the page, so typecheck/build stay green (the component is simply unused until Task 3). + +**Files:** +- Modify: `site/components/SvgSprite/SvgSprite.tsx` +- Create: `site/components/Features/DockerVisual.tsx` + +**Interfaces:** +- Consumes: `GlassPanel`, `PanelSep`, `UiRow`, `RowMeta`, `SizeLabel` from `@/components/ui-mock`; `Icon` from `@/components/Icon`. +- Produces: `export function DockerVisual(): JSX.Element` (no props) — used by Task 3; and sprite symbol `id="i-docker"` — usable via ``. + +- [ ] **Step 1: Add the `i-docker` symbol to the sprite** + +In `site/components/SvgSprite/SvgSprite.tsx`, inside the `SYMBOLS` template string, add this line next to `i-layers` (stroke line-icon style, viewBox `0 0 24 24`, `currentColor`): +``` + +``` + +- [ ] **Step 2: Create `DockerVisual.tsx`** + +Mirrors `PackagesVisual` structure: a `GlassPanel` with a header (Docker tab active), then two project groups with a logo tile, name, resource sub-line, and size. Create `site/components/Features/DockerVisual.tsx`: +```tsx +import { Icon } from "@/components/Icon"; +import { + GlassPanel, + PanelSep, + RowMeta, + SizeLabel, + UiRow, +} from "@/components/ui-mock"; + +// Feature 5: the Docker tab, resources grouped by the project they belong to. +export function DockerVisual() { + return ( + +
+ + + +
+ + Projects + + + Caches + + + Docker + +
+
+ +
+ + 23.6 GB · grouped by project + +
+
+ + + 🐳 + + + + + + + 🐘 + + + + + + + + + + + +
+
+ ); +} +``` + +- [ ] **Step 3: Verify it compiles and the icon resolves** + +Run: `cd site && pnpm typecheck` +Expected: PASS. (DockerVisual is not imported anywhere yet; that is fine, it is a plain export, not an unused local.) + +- [ ] **Step 4: Commit** + +```bash +git add site/components/SvgSprite/SvgSprite.tsx site/components/Features/DockerVisual.tsx +git commit -m "feat(site): add the Docker-by-project feature visual + docker sprite icon" +``` + +--- + +### Task 3: Add the Docker feature row (widen the tuple, all 5 locales) + +Widen `features` from a 4-tuple to a 5-tuple, add the Docker entry to every dictionary, and render the fifth row. The type change forces all 5 dictionaries at once (D8). + +**Files:** +- Modify: `site/lib/i18n/i18n.types.ts` +- Modify: `site/lib/i18n/dictionaries/en.tsx` then `fr.tsx`, `es.tsx`, `de.tsx`, `pt.tsx` +- Modify: `site/components/Features/Features.tsx` + +**Interfaces:** +- Consumes: `DockerVisual` from `./DockerVisual` (Task 2). +- Produces: `Dictionary.features` becomes `[FeatureCopy, FeatureCopy, FeatureCopy, FeatureCopy, FeatureCopy]` (5-tuple). + +- [ ] **Step 1: Widen the `features` type** + +In `site/lib/i18n/i18n.types.ts`, change the `features` field on the `Dictionary` interface from a 4-tuple to a 5-tuple: +```ts + /** Exactly five feature rows, in display order (row 5 is Docker). */ + features: [FeatureCopy, FeatureCopy, FeatureCopy, FeatureCopy, FeatureCopy]; +``` + +- [ ] **Step 2: Run typecheck to see it fail across all locales** + +Run: `cd site && pnpm typecheck` +Expected: FAIL — every `dictionaries/*.tsx` errors that its `features` 4-tuple is not assignable to the 5-tuple. This confirms the contract is enforcing all locales. + +- [ ] **Step 3: Add the English Docker feature (5th entry) in `en.tsx`** + +Append as the 5th element of the `features: [ … ]` array in `en.tsx` (after the Packages entry): +```tsx + { + tagline: "Beyond node_modules", + heading: "Your Docker disk, grouped by project.", + body: "Docker quietly hoards gigabytes in images, volumes, containers and build cache. Open the Docker tab to see it grouped by the project each resource belongs to, with real logos, sizes and in-use badges. Reclaim dangling images, stopped containers and unused volumes in a click, with the same safe confirmations as the rest of TidyDisk.", + bullets: [ + "Images, volumes, containers and build cache, each with its real size", + <> + Resources grouped by project from Compose labels and used-by + links + , + "Safe, typed confirmations before anything is permanently removed", + ], + }, +``` + +- [ ] **Step 4: Add the translated Docker feature to the other 4 locales** + +In `fr.tsx`, `es.tsx`, `de.tsx`, `pt.tsx`, append a 5th `features` element that translates the English entry above. Rules: keep the second bullet's `grouped by project` structure (translate the wrapped text); do not translate `Docker`, `node_modules`, `Compose`; no em dashes; match the dictionary's tone. + +- [ ] **Step 5: Render the fifth row in `Features.tsx`** + +In `site/components/Features/Features.tsx`: import `DockerVisual`, destructure the 5th feature, and add a 5th `FeatureRow`. Change the destructure line and add the row: +```tsx +import { DockerVisual } from "./DockerVisual"; +// … + const [notif, launcher, reclaim, packages, docker] = dict.features; +``` +Then add, after the Packages `FeatureRow` (num "04", which is `flip`), a fifth row (num "05", not flipped so it alternates correctly, with `id="docker"`): +```tsx + } + /> +``` + +- [ ] **Step 6: Verify all locales compile and build** + +Run: `cd site && pnpm typecheck && pnpm build` +Expected: both PASS. The 5-tuple is satisfied by all 5 dictionaries and the row renders. + +- [ ] **Step 7: Em-dash sweep + commit** + +Run: `cd site && grep -n "—" lib/i18n/dictionaries/*.tsx` (expect no new matches). +```bash +git add site/lib/i18n site/components/Features/Features.tsx +git commit -m "feat(site): add Docker as the fifth feature row (5 locales)" +``` + +--- + +### Task 4: Add the `areas` dictionary slice (type + all 5 locales) + +Add the typed `areas` slice and populate every dictionary. No component yet, so this ends green with the field present and implemented but unused (Task 5 renders it). Covers D4's copy. + +**Files:** +- Modify: `site/lib/i18n/i18n.types.ts` +- Modify: `site/lib/i18n/dictionaries/en.tsx` then `fr.tsx`, `es.tsx`, `de.tsx`, `pt.tsx` + +**Interfaces:** +- Produces: `AreaCardCopy` = `{ title: string; copy: string }`; `AreasCopy` = `{ kicker: string; heading: ReactNode; lead: string; cards: [AreaCardCopy, AreaCardCopy, AreaCardCopy, AreaCardCopy] }`; `Dictionary.areas: AreasCopy`. Card order is fixed: Projects, Caches, Packages, Docker. + +- [ ] **Step 1: Add the types** + +In `site/lib/i18n/i18n.types.ts`, add before the `Dictionary` interface: +```ts +/** One card in the four-up "areas" section (icon + accent flag stay in the component). */ +export interface AreaCardCopy { + title: string; + copy: string; +} + +export interface AreasCopy { + kicker: string; + /** Accent `` around the last words. */ + heading: ReactNode; + lead: string; + /** Fixed order: Projects, Caches, Packages, Docker. */ + cards: [AreaCardCopy, AreaCardCopy, AreaCardCopy, AreaCardCopy]; +} +``` +Then add the field to `Dictionary` (place it right after `band`): +```ts + areas: AreasCopy; +``` + +- [ ] **Step 2: Run typecheck to confirm it fails until all locales implement it** + +Run: `cd site && pnpm typecheck` +Expected: FAIL — every dictionary is missing `areas`. + +- [ ] **Step 3: Add the English `areas` slice in `en.tsx`** + +Add after the `band: { … },` block: +```tsx + areas: { + kicker: "One tool, every kind of dev junk", + heading: ( + <> + Four places your disk quietly{" "} + fills up. + + ), + lead: "TidyDisk watches all of them and gives the space back, safely.", + cards: [ + { + title: "Projects", + copy: "Heavy, stale node_modules ranked by the real bytes you would free.", + }, + { + title: "Caches", + copy: "Your pnpm store and Docker build cache, pruned in one safe click.", + }, + { + title: "Packages", + copy: "A machine-wide dependency inventory: version drift, duplicates, and security advisories.", + }, + { + title: "Docker", + copy: "Images, volumes, containers and build cache, grouped by the project they belong to.", + }, + ], + }, +``` + +- [ ] **Step 4: Add the translated `areas` slice to the other 4 locales** + +In `fr.tsx`, `es.tsx`, `de.tsx`, `pt.tsx`, add the `areas` slice translating the English. Rules: keep the accent `…` in `heading` (translate the wrapped words); the card `title`s "Projects / Caches / Packages / Docker" stay as the app's tab names (do not translate `Docker`; translate the others only if the app UI/other dictionary entries already do, otherwise keep them as-is for consistency with the product); do not translate `node_modules`, `pnpm`, `Docker`; no em dashes. + +- [ ] **Step 5: Verify all locales compile and build** + +Run: `cd site && pnpm typecheck && pnpm build` +Expected: both PASS (field implemented in all 5, unused for now). + +- [ ] **Step 6: Em-dash sweep + commit** + +Run: `cd site && grep -n "—" lib/i18n/dictionaries/*.tsx` (expect no new matches). +```bash +git add site/lib/i18n +git commit -m "feat(site): add the four-area 'disk waste' dictionary slice (5 locales)" +``` + +--- + +### Task 5: Build and wire the Areas section + +Create the four-peer-cards section and render it between the statement band and the features (D4). Reuses the site's `PixelMeter` for the size bars. + +**Files:** +- Create: `site/components/Areas/Areas.tsx`, `Areas.types.ts`, `Areas.constants.ts`, `index.ts` +- Modify: `site/components/pages/HomePage/HomePage.tsx` + +**Interfaces:** +- Consumes: `dict.areas` (Task 4); `PixelMeter` from `@/components/PixelMeter` (props `used`, `threshold`, `cells?`, `size?`); `Icon`, `Wrap`, `SectionHead`; `Dictionary` type. +- Produces: `export function Areas({ dict }: { dict: Dictionary }): JSX.Element`. + +- [ ] **Step 1: Create `Areas.types.ts`** + +```ts +import type { Dictionary } from "@/lib/i18n"; + +export interface AreasProps { + dict: Dictionary; +} +``` + +- [ ] **Step 2: Create `Areas.constants.ts`** + +Per-card icon id + a size/severity descriptor. Icons come from the sprite (Task 2 added `i-docker`); the others already exist. +```ts +// Per-card presentation for the four areas (copy lives in dict.areas.cards). +// `bar` drives the mini visual: a PixelMeter for size areas, a severity strip +// for Packages. Order matches dict.areas.cards: Projects, Caches, Packages, Docker. +export const AREA_ICONS = ["i-hdd", "i-layers", "i-box", "i-docker"] as const; + +export type AreaBar = + | { kind: "size"; used: number; threshold: number; label: string } + | { kind: "sev"; label: string }; + +export const AREA_BARS: [AreaBar, AreaBar, AreaBar, AreaBar] = [ + { kind: "size", used: 41.2, threshold: 30, label: "41.2 GB" }, + { kind: "size", used: 6.4, threshold: 10, label: "6.4 GB" }, + { kind: "sev", label: "7 vulnerable" }, + { kind: "size", used: 23.6, threshold: 20, label: "23.6 GB" }, +]; + +/** Docker is the newest area; give its card the accent border. */ +export const AREA_ACCENT = [false, false, false, true] as const; +``` + +- [ ] **Step 3: Create `Areas.tsx`** + +```tsx +import { Icon } from "@/components/Icon"; +import { Wrap } from "@/components/Wrap"; +import { SectionHead } from "@/components/SectionHead"; +import { PixelMeter } from "@/components/PixelMeter"; +import { AREA_ACCENT, AREA_BARS, AREA_ICONS } from "./Areas.constants"; +import type { AreasProps } from "./Areas.types"; + +const DELAY = ["", " d1", " d2", " d3"]; + +export function Areas({ dict }: AreasProps) { + const areas = dict.areas; + return ( +
+ + +
+ {areas.cards.map((card, i) => { + const bar = AREA_BARS[i]; + return ( +
+
+ +
+

+ {card.title} +

+

+ {card.copy} +

+ {bar.kind === "size" ? ( + + ) : ( +
+ + + + +
+ )} +
+ {bar.label} +
+
+ ); + })} +
+
+
+ ); +} +``` + +- [ ] **Step 4: Create `index.ts`** + +```ts +export { Areas } from "./Areas"; +export type { AreasProps } from "./Areas.types"; +``` + +- [ ] **Step 5: Wire into `HomePage.tsx`** + +Add the import with the other section imports: +```tsx +import { Areas } from "@/components/Areas"; +``` +Then render it between `` and ``: +```tsx + + + +``` + +- [ ] **Step 6: Verify** + +Run: `cd site && pnpm typecheck && pnpm build` +Expected: both PASS. + +- [ ] **Step 7: Commit** + +```bash +git add site/components/Areas site/components/pages/HomePage/HomePage.tsx +git commit -m "feat(site): four-area 'disk waste' section between band and features" +``` + +--- + +### Task 6: Rebuild the hero mockup to the new panel dashboard + +Replace the hero's rendering of the old reclaimable-list panel (`AppPanel`) with a new dashboard mock matching the shipped app: a "Tracked on disk" hero number plus four area bars (D7). `AppPanel` is left untouched because `app/og/page.tsx` still uses it. + +**Files:** +- Create: `site/components/Hero/HeroPanel.tsx` +- Modify: `site/components/Hero/HeroScene.tsx` + +**Interfaces:** +- Consumes: `PixelMeter` from `@/components/PixelMeter`; `Icon`; `PanelSep` from `@/components/ui-mock`. +- Produces: `export function HeroPanel(): JSX.Element` (no props), rendered inside the hero's existing ``. + +- [ ] **Step 1: Create `HeroPanel.tsx`** + +A compact dashboard: the aggregate "Tracked on disk" number + a full-width meter, then four click-through-style rows each with a label, a small meter (or severity strip), a value, and a chevron. Matches the real panel. +```tsx +import { Icon } from "@/components/Icon"; +import { PanelSep } from "@/components/ui-mock"; +import { PixelMeter } from "@/components/PixelMeter"; + +const ROWS = [ + { name: "Projects", used: 41.2, threshold: 30, value: "41.2 GB", sev: false }, + { name: "Caches", used: 6.4, threshold: 10, value: "6.4 GB", sev: false }, + { name: "Packages", used: 0, threshold: 1, value: "7 vuln", sev: true }, + { name: "Docker", used: 23.6, threshold: 20, value: "23.6 GB", sev: false }, +]; + +// The hero's recreation of the new read-only menu bar panel dashboard: +// a "Tracked on disk" aggregate plus four click-through area rows. +export function HeroPanel() { + return ( +
+
+ Tracked on disk +
+
+ + 71.2 GB + + + 21.2 GB over + +
+ + +
+ {ROWS.map((r) => ( +
+ + {r.name} + + + {r.sev ? ( + + + + + + ) : ( + + )} + + + {r.value} + + + + +
+ ))} +
+
+ ); +} +``` + +- [ ] **Step 2: Swap it into `HeroScene.tsx`** + +In `site/components/Hero/HeroScene.tsx`: replace the `import { AppPanel } from "@/components/AppPanel";` line with `import { HeroPanel } from "./HeroPanel";`, and replace `` inside the dropdown `` with ``. Leave everything else (menu bar, floating Packages card, toast) unchanged. + +- [ ] **Step 3: Verify AppPanel is still used elsewhere (not orphaned incorrectly)** + +Run: `cd site && grep -rln "AppPanel" components app | grep -v "components/AppPanel/"` +Expected: `app/og/page.tsx` still references it (so it must NOT be deleted). `HeroScene.tsx` should no longer appear. + +- [ ] **Step 4: Verify compile + build** + +Run: `cd site && pnpm typecheck && pnpm build` +Expected: both PASS. + +- [ ] **Step 5: Commit** + +```bash +git add site/components/Hero +git commit -m "feat(site): hero mockup shows the new panel dashboard, not the old list" +``` + +--- + +### Task 7: Whole-page visual verification + +No code change unless the review finds a rendering issue. Confirms the assembled page renders correctly across locales (the spec's verification section). + +**Files:** none (verification only; fixes, if any, go back to the relevant task's files). + +- [ ] **Step 1: Full gate** + +Run: `cd site && pnpm typecheck && pnpm build && pnpm lint` +Expected: typecheck + build PASS; lint clean (report any pre-existing warnings unrelated to this work). + +- [ ] **Step 2: Visual review in a real browser** + +Start the dev server (`cd site && pnpm dev`) or serve the build, and render `/` and `/fr` (a non-English locale). Confirm: +- Hero mockup shows the "Tracked on disk" dashboard with four bars (not the old list). +- The statement band reads the evolved line (node_modules joke + Docker/caches widening). +- The Areas section shows four cards (Projects / Caches / Packages / Docker), Docker with the accent border, each with a mini bar. +- The Docker feature row renders with the by-project `DockerVisual`. +- The coming-soon pill reads "Next up: npm, yarn & bun caches …". +- On `/fr`, the same structure holds with translated text and identical JSX (accent spans, `` tokens intact). + +Capture a screenshot of `/` for the record (as prior site work did with Playwright). + +- [ ] **Step 3: Final em-dash sweep across the changed copy** + +Run: `cd site && grep -n "—" lib/i18n/dictionaries/*.tsx` +Expected: no matches introduced by this work. + +- [ ] **Step 4: STATUS + done** + +This is a docs update handled by the controller after the plan completes (bump STATUS.html, note the landing-page repositioning + a native-speaker-proofread follow-up userAction). No commit in this task beyond any review fixes. + +--- + +## Final Verification + +- [ ] `cd site && pnpm typecheck` — clean (proves all 5 locales implement the `areas` slice and the 5-tuple `features`) +- [ ] `cd site && pnpm build` — static export of `/`, `/fr`, `/es`, `/de`, `/pt` succeeds +- [ ] `cd site && pnpm lint` — clean +- [ ] No em dashes introduced in any dictionary (`grep -n "—" site/lib/i18n/dictionaries/*.tsx`) +- [ ] `AppPanel` untouched and still used by `app/og/page.tsx`; hero uses `HeroPanel` +- [ ] Visual review of `/` and `/fr` confirms the repositioning renders in-theme + +**Follow-up (not blocking, track as a userAction):** native-speaker proofread of the new fr/es/de/pt Docker/areas copy (same caveat as the rest of the machine-translated site). diff --git a/docs/superpowers/specs/2026-07-16-landing-page-all-disk-waste-design.md b/docs/superpowers/specs/2026-07-16-landing-page-all-disk-waste-design.md new file mode 100644 index 0000000..b329aba --- /dev/null +++ b/docs/superpowers/specs/2026-07-16-landing-page-all-disk-waste-design.md @@ -0,0 +1,186 @@ +# Landing page repositioning: "all developer disk waste" + +**Date:** 2026-07-16 +**Status:** approved, ready for planning +**Scope:** `site/` only (Next.js + Tailwind v4 marketing site). No app code. + +## Problem + +TidyDisk shipped well beyond `node_modules`: Docker cleanup (images / volumes / +containers / build cache, grouped by project), a real Caches surface (pnpm store + +Docker build cache), per-tab headline data-viz, and a read-only menu bar panel +dashboard. The landing page still pitches the product as a `node_modules` cleaner: +the statement band literally reads "node_modules is the heaviest object in the known +universe," Docker appears nowhere on the page, and the "coming soon" pill still +promises caches and build outputs as future work when some have shipped. Worse, the +hero mockup renders the *old* reclaimable-list panel, a UI that no longer exists. + +Reposition the page so the top of the funnel pitches **all developer disk waste** +(projects + caches + packages + Docker), with `node_modules` as the lead example +rather than the whole product. + +## Decisions + +Every decision below was made explicitly during brainstorming, with the visual +companion used to choose the Areas layout. + +### D1 — Full repositioning of the core message (not a feature bolt-on) + +Rework the top-of-page copy and add a centerpiece section, rather than just appending +Docker to the existing feature list. The headline "A tidy disk, without thinking about +it." is already tool-agnostic and **stays**. + +### D2 — Statement band evolves, keeps the joke + +`band.statement` keeps "node_modules is the heaviest object in the known universe" as +the setup, then widens the punchline: it is not alone (Docker images, build caches, +dead projects pile up too) and TidyDisk clears all of it. Keeps the memorable hook +while making breadth the point. (Rejected: replacing it with a breadth-first line, or +leaving it node_modules-only.) + +### D3 — Hero subhead broadens + +`hero.body` names the surface: dev work leaves behind heavy `node_modules`, Docker +images, build caches, and forgotten experiments; TidyDisk watches from the menu bar +and gives it back in one click, safely, to the Trash, never `rm -rf`. Keeps the +existing safety close (the `rm -rf` ending the type requires). + +### D4 — New "Areas" section: four peer cards (companion choice A) + +A new section placed **immediately after the statement band, before the Features +rows**. Four equal cards — **Projects / Caches / Packages / Docker** — each with an +icon, a one-line description, and a size/severity mini-bar mirroring the app's new +dashboard rows. Docker carries a subtle accent border as the newest area. This is the +visual embodiment of "it is not just node_modules." (Rejected in the companion: an +aggregate segmented bar (B), and a tabbed app-window mock (C) which would compete with +the hero mockup for "show the app" duty.) + +### D5 — Docker spotlight feature row + +Add a fifth row to the existing alternating `Features` section, with a hand-built +`DockerVisual` (matching the existing `NotifVisual`/`LauncherVisual`/`ReclaimVisual`/ +`PackagesVisual` style — inline TSX/SVG, no image assets) showing Docker resources +**grouped by project** (project rows with logos, resource-type badges, sizes) — the +story the app tells. This changes the `features` tuple from 4 to 5 entries (see D8). + +### D6 — Caches promoted from "coming soon" to real + +The Caches area is covered as a real, shipped surface (its Areas card in D4, plus its +existing grid presence). The `grid.comingSoonText` pill is rewritten so it teases +**only genuinely-future** work (npm / yarn / bun caches, `.next` / `dist` build +outputs) and no longer implies the pnpm store or Docker build cache are unshipped. No +claiming shipped things as future; no teasing done things as coming. Caches does **not** +get its own spotlight feature row (companion decision) — its promotion is via the Areas +card + the corrected pill. + +### D7 — Hero mockup rebuilt for accuracy + +`Hero/HeroScene.tsx` currently renders the old reclaimable-list panel (via `AppPanel`), +which no longer exists in the shipped app. Rebuild the in-hero mockup to show the new +menu bar **panel dashboard**: the "Tracked on disk" aggregate hero plus the four +click-through area bars (Projects / Caches / Packages / Docker). This is a +component/visual change, not copy. It doubles as live proof of the repositioning at the +very top of the page. + +### D8 — All 5 locales in this pass + +en/fr/es/de/pt are all live with hreflang and every string lives in all 5 dictionaries. +English (`dictionaries/en.tsx`, the byte-for-byte source of truth) is authored first, +then fr/es/de/pt are translated in the same effort. The `Dictionary` type contract +(`lib/i18n/i18n.types.ts`) **forces** this: changing the `features` tuple to 5 entries +and adding the `areas` slice will not compile until all 5 dictionaries implement them. +Constraints inherited from the dictionary header comment: +- `ReactNode` fields reproduce the **same JSX structure** across locales (identical + tags/classNames: ``, ``, ``, the accent ``); + only human text is translated. +- Technical tokens are **not** translated: `node_modules`, `rm -rf`, `pnpm`, + `npm install`, `.next`, `dist`, paths, and brand names (`Docker`). Prices stay `€19`. +- **No em dashes in any language.** + +### D9 — Also broaden SEO metadata + +`meta.description` (plain string, all 5 locales) currently enumerates "every +node_modules folder, your pnpm store, and every installed package" — it is broadened to +add Docker and caches/build artifacts to that enumeration, so the SoftwareApplication +JSON-LD (`HomePage.tsx`) and page `` no longer describe a node_modules-only tool. +`meta.title` ("see what is eating your dev disk, reclaim it in one click") is already +tool-agnostic and **stays unchanged**. The two JSON-LD offers (free scan / €19) are +unchanged. + +## Architecture + +The site is fully component-assembled and dictionary-driven; nothing here changes that. +Copy lives only in dictionaries; components are presentational and read `dict` slices. + +### New + +- **`site/components/Areas/`** — the peer-cards section (`Areas.tsx`, `Areas.types.ts`, + `index.ts`, optional `Areas.constants.ts` for the per-card icon ids/accent flags). + Reads a new `dict.areas` slice. One card component or an inline map over the four + cards; follows the existing `FeatureGrid` card styling (rounded-2xl border, 42px + accent icon tile, display-font title) and the `reveal`/`d1`/`d2` scroll-in pattern. +- **`site/components/Features/DockerVisual.tsx`** — the hand-built Docker-by-project + mockup for the new feature row, sibling to the other `*Visual` components. + +### Changed + +- **`site/lib/i18n/i18n.types.ts`** — add `AreaCardCopy` + `AreasCopy` interfaces and an + `areas: AreasCopy` field on `Dictionary`; widen `features` from a 4-tuple to a + **5-tuple**. `AreasCopy` holds a `kicker`, an accent `heading` (`ReactNode`), a `lead`, + and exactly four `AreaCardCopy` entries (`title`, `copy`; icon + accent flag stay in + the component per the existing grid-card convention). +- **`site/lib/i18n/dictionaries/{en,fr,es,de,pt}.tsx`** — for each: broadened `hero.body` + (D3), evolved `band.statement` (D2), a new `areas` slice (D4), a 5th `features` entry + for Docker (D5), rewritten `grid.comingSoonText` (D6), broadened `meta.description` + (D9). en authored first; the other four translated from it. +- **`site/components/Features/Features.tsx`** — destructure the 5th feature and render a + fifth `FeatureRow` (num "05", alternating `flip`, `visual={}`), giving + it an `id="docker"` anchor. +- **`site/components/pages/HomePage/HomePage.tsx`** — import and render `` + between `` and ``. +- **`site/components/Hero/HeroScene.tsx`** — rebuild the in-hero mockup as the new panel + dashboard (D7). If the shared `AppPanel`/`ui-mock` primitives no longer fit, add a + focused mock local to HeroScene rather than reshaping shared primitives used elsewhere. + +### Unchanged / out of scope + +- `WhyLifecycle`, `HowItWorks`, `Download` (pricing), `FinalCta`, `Footer`, `Navbar`, + blog. Navbar gains no new link (YAGNI; the Areas section is visible immediately below + the hero and needs no jump link). +- The per-tab data-viz and the panel dashboard are reflected only through the hero + mockup (D7) and the Areas cards, not as their own sections (companion decision). +- No new image assets; all visuals stay inline TSX/SVG per the site's convention. + +## Testing / verification + +The site has no unit tests for marketing components; correctness is enforced by the +type system and the build, plus visual review. Verification steps: + +1. **`pnpm typecheck`** (in `site/`) — the single strongest gate: the `Dictionary` + contract fails to compile until the `areas` slice and the 5-tuple `features` are + implemented in **all five** dictionaries with the right shapes. A green typecheck + proves no locale was left behind and no JSX-structure field is missing. +2. **`pnpm build`** — the static export of `/`, `/fr`, `/es`, `/de`, `/pt` succeeds. +3. **Visual review** — render `/` and at least one non-English locale (e.g. `/fr`) in a + real browser (Playwright, as prior site work did): confirm the evolved band, the new + Areas cards, the Docker feature row + visual, the rebuilt hero mockup, and the + corrected coming-soon pill all render in-theme and the JSX structure held across + locales. +4. **Em-dash sweep** of the changed dictionary copy in all five locales. + +## Risks + +- **5-tuple cascade:** widening `features` touches every dictionary and `Features.tsx`. + The compiler catches omissions, so the risk is tedium, not silent breakage. +- **Translation quality:** fr/es/de/pt Docker/areas copy is machine-translated (same + caveat as the rest of the site); a native-speaker skim is a reasonable follow-up + `userAction`, not a blocker. +- **Hero mockup scope creep:** rebuilding `HeroScene` is the least bounded task. Keep the + mock faithful-but-simple (hero number + four bars), not a pixel-clone of the app, and + do not reshape shared `ui-mock` primitives other sections depend on. + +## Out of scope + +- App code, the release, and any pricing/offer change. +- New locales or a Docker-specific blog article (the blog already mentions Docker). +- A native-speaker proofread of the new translated copy (track as a follow-up). diff --git a/site/components/Areas/Areas.constants.ts b/site/components/Areas/Areas.constants.ts new file mode 100644 index 0000000..74dbd55 --- /dev/null +++ b/site/components/Areas/Areas.constants.ts @@ -0,0 +1,18 @@ +// Per-card presentation for the four areas (copy lives in dict.areas.cards). +// `bar` drives the mini visual: a PixelMeter for size areas, a severity strip +// for Packages. Order matches dict.areas.cards: Projects, Caches, Packages, Docker. +export const AREA_ICONS = ["i-hdd", "i-layers", "i-box", "i-docker"] as const; + +export type AreaBar = + | { kind: "size"; used: number; threshold: number; label: string } + | { kind: "sev"; label: string }; + +export const AREA_BARS: [AreaBar, AreaBar, AreaBar, AreaBar] = [ + { kind: "size", used: 41.2, threshold: 30, label: "41.2 GB" }, + { kind: "size", used: 6.4, threshold: 10, label: "6.4 GB" }, + { kind: "sev", label: "7 vulnerable" }, + { kind: "size", used: 23.6, threshold: 20, label: "23.6 GB" }, +]; + +/** Docker is the newest area; give its card the accent border. */ +export const AREA_ACCENT = [false, false, false, true] as const; diff --git a/site/components/Areas/Areas.tsx b/site/components/Areas/Areas.tsx new file mode 100644 index 0000000..aebbecf --- /dev/null +++ b/site/components/Areas/Areas.tsx @@ -0,0 +1,67 @@ +import { Icon } from "@/components/Icon"; +import { Wrap } from "@/components/Wrap"; +import { SectionHead } from "@/components/SectionHead"; +import { PixelMeter } from "@/components/PixelMeter"; +import { AREA_ACCENT, AREA_BARS, AREA_ICONS } from "./Areas.constants"; +import type { AreasProps } from "./Areas.types"; + +const DELAY = ["", " d1", " d2", " d3"]; + +export function Areas({ dict }: AreasProps) { + const areas = dict.areas; + return ( +
+ + +
+ {areas.cards.map((card, i) => { + const bar = AREA_BARS[i]; + return ( +
+
+ +
+

+ {card.title} +

+

+ {card.copy} +

+ {bar.kind === "size" ? ( + + ) : ( +
+ + + + +
+ )} +
+ {bar.label} +
+
+ ); + })} +
+
+
+ ); +} diff --git a/site/components/Areas/Areas.types.ts b/site/components/Areas/Areas.types.ts new file mode 100644 index 0000000..fd95bd6 --- /dev/null +++ b/site/components/Areas/Areas.types.ts @@ -0,0 +1,5 @@ +import type { Dictionary } from "@/lib/i18n"; + +export interface AreasProps { + dict: Dictionary; +} diff --git a/site/components/Areas/index.ts b/site/components/Areas/index.ts new file mode 100644 index 0000000..db3afae --- /dev/null +++ b/site/components/Areas/index.ts @@ -0,0 +1,2 @@ +export { Areas } from "./Areas"; +export type { AreasProps } from "./Areas.types"; diff --git a/site/components/Features/DockerVisual.tsx b/site/components/Features/DockerVisual.tsx new file mode 100644 index 0000000..d2ce9f9 --- /dev/null +++ b/site/components/Features/DockerVisual.tsx @@ -0,0 +1,61 @@ +import { Icon } from "@/components/Icon"; +import { + GlassPanel, + PanelSep, + RowMeta, + SizeLabel, + UiRow, +} from "@/components/ui-mock"; + +// Feature 5: the Docker tab, resources grouped by the project they belong to. +export function DockerVisual() { + return ( + +
+ + + +
+ + Projects + + + Caches + + + Docker + +
+
+ +
+ + 23.6 GB · grouped by project + +
+
+ + + 🐳 + + + + + + + 🐘 + + + + + + + + + + + +
+
+ ); +} diff --git a/site/components/Features/Features.tsx b/site/components/Features/Features.tsx index 5d64755..647b9fe 100644 --- a/site/components/Features/Features.tsx +++ b/site/components/Features/Features.tsx @@ -4,6 +4,7 @@ import { NotifVisual } from "./NotifVisual"; import { LauncherVisual } from "./LauncherVisual"; import { ReclaimVisual } from "./ReclaimVisual"; import { PackagesVisual } from "./PackagesVisual"; +import { DockerVisual } from "./DockerVisual"; import type { Dictionary } from "@/lib/i18n"; export interface FeaturesProps { @@ -11,7 +12,7 @@ export interface FeaturesProps { } export function Features({ dict }: FeaturesProps) { - const [notif, launcher, reclaim, packages] = dict.features; + const [notif, launcher, reclaim, packages, docker] = dict.features; return (
@@ -50,6 +51,15 @@ export function Features({ dict }: FeaturesProps) { bullets={packages.bullets} visual={} /> + } + />
); diff --git a/site/components/Hero/HeroPanel.tsx b/site/components/Hero/HeroPanel.tsx new file mode 100644 index 0000000..9593bab --- /dev/null +++ b/site/components/Hero/HeroPanel.tsx @@ -0,0 +1,58 @@ +import { Icon } from "@/components/Icon"; +import { PanelSep } from "@/components/ui-mock"; +import { PixelMeter } from "@/components/PixelMeter"; + +const ROWS = [ + { name: "Projects", used: 41.2, threshold: 30, value: "41.2 GB", sev: false }, + { name: "Caches", used: 6.4, threshold: 10, value: "6.4 GB", sev: false }, + { name: "Packages", used: 0, threshold: 1, value: "7 vuln", sev: true }, + { name: "Docker", used: 23.6, threshold: 20, value: "23.6 GB", sev: false }, +]; + +// The hero's recreation of the new read-only menu bar panel dashboard: +// a "Tracked on disk" aggregate plus four click-through area rows. +export function HeroPanel() { + return ( +
+
+ Tracked on disk +
+
+ + 71.2 GB + + + 21.2 GB over + +
+ + +
+ {ROWS.map((r) => ( +
+ + {r.name} + + + {r.sev ? ( + + + + + + ) : ( + + )} + + + {r.value} + + + + +
+ ))} +
+
+ ); +} diff --git a/site/components/Hero/HeroScene.tsx b/site/components/Hero/HeroScene.tsx index c2e1ccd..e7a4924 100644 --- a/site/components/Hero/HeroScene.tsx +++ b/site/components/Hero/HeroScene.tsx @@ -1,5 +1,5 @@ import { Icon } from "@/components/Icon"; -import { AppPanel } from "@/components/AppPanel"; +import { HeroPanel } from "./HeroPanel"; import { GlassPanel, PanelSep, @@ -49,7 +49,7 @@ export function HeroScene() { {/* dropdown: recreation of the real menu-bar panel */} - + {/* floating Packages card: the whole-machine inventory */} diff --git a/site/components/SvgSprite/SvgSprite.tsx b/site/components/SvgSprite/SvgSprite.tsx index e79341f..016972e 100644 --- a/site/components/SvgSprite/SvgSprite.tsx +++ b/site/components/SvgSprite/SvgSprite.tsx @@ -35,6 +35,7 @@ const SYMBOLS = ` + `; export function SvgSprite() { diff --git a/site/components/pages/HomePage/HomePage.tsx b/site/components/pages/HomePage/HomePage.tsx index d3d00d3..179f714 100644 --- a/site/components/pages/HomePage/HomePage.tsx +++ b/site/components/pages/HomePage/HomePage.tsx @@ -1,6 +1,7 @@ import { Navbar } from "@/components/Navbar"; import { Hero } from "@/components/Hero"; import { StatementBand } from "@/components/StatementBand"; +import { Areas } from "@/components/Areas"; import { Features } from "@/components/Features"; import { FeatureGrid } from "@/components/FeatureGrid"; import { WhyLifecycle } from "@/components/WhyLifecycle"; @@ -49,6 +50,7 @@ export function HomePage({ locale }: { locale: Locale }) {
+ diff --git a/site/lib/i18n/dictionaries/de.tsx b/site/lib/i18n/dictionaries/de.tsx index 9981449..d28eae8 100644 --- a/site/lib/i18n/dictionaries/de.tsx +++ b/site/lib/i18n/dictionaries/de.tsx @@ -8,7 +8,7 @@ export const de: Dictionary = { title: "TidyDisk: sieh, was deine Dev-Festplatte frisst, hol sie dir mit einem Klick zurück", description: - "TidyDisk zeigt in der Menüleiste, was dein Dev-Mac kostet: node_modules, pnpm-Store, Pakete. Kostenlos scannen, für 19 Euro sicher aufräumen, nie rm -rf.", + "TidyDisk lebt in deiner macOS-Menüleiste und zeigt, was deine Dev-Projekte wirklich kosten: jeden node_modules-Ordner, deinen pnpm-Store und die Caches der Paketmanager, Docker-Images und -Volumes, und jedes installierte Paket. Kostenlos scannen. Aufräumen mit einem Klick dank Lifetime-Lizenz für 19 Euro. Sicher, in den Papierkorb, nie rm -rf.", blogTitle: "TidyDisk-Blog: saubere Dev-Festplatten", blogDescription: "Praktische Anleitungen zu node_modules, Paketmanager-Speicherverbrauch und wie du Speicherplatz auf dem Dev-Mac zurückgewinnst. Von den Machern von TidyDisk.", @@ -37,10 +37,11 @@ export const de: Dictionary = { ), body: ( <> - Dev-Arbeit frisst deinen Mac still und leise: alte Projekte, schwere - Abhängigkeiten, vergessene Experimente. TidyDisk behält das aus der - Menüleiste im Blick und gibt dir den Speicher mit einem Klick - zurück. Sicher, in den Papierkorb, nie rm -rf. + Dev-Arbeit frisst deinen Mac still und leise: schwere{" "} + node_modules, Docker-Images, Build-Caches, vergessene + Experimente. TidyDisk behält das aus der Menüleiste im Blick und + gibt dir den Speicher mit einem Klick zurück. Sicher, in den + Papierkorb, nie rm -rf. ), downloadCta: "Für macOS herunterladen", @@ -52,11 +53,42 @@ export const de: Dictionary = { statement: ( <> node_modules ist das schwerste Objekt im bekannten - Universum. Wir helfen dir, es zu löschen. + Universum. Aber es ist nicht allein: Docker-Images, Build-Caches + und tote Projekte häufen sich ebenfalls an.{" "} + TidyDisk räumt das alles weg. ), }, + areas: { + kicker: "Ein Tool für jede Art von Dev-Müll", + heading: ( + <> + Vier Orte, an denen sich deine Festplatte{" "} + im Stillen füllt. + + ), + lead: "TidyDisk behält alle vier im Blick und gibt dir den Speicher sicher zurück.", + cards: [ + { + title: "Projects", + copy: "Schwere, veraltete node_modules, sortiert nach den echten Bytes, die du freigeben würdest.", + }, + { + title: "Caches", + copy: "Dein pnpm-Store und der Docker-Build-Cache, bereinigt mit einem sicheren Klick.", + }, + { + title: "Pakete", + copy: "Eine rechnerweite Bestandsaufnahme aller Abhängigkeiten: auseinanderdriftende Versionen, Duplikate und Sicherheitswarnungen.", + }, + { + title: "Docker", + copy: "Images, Volumes, Container und Build-Cache, gruppiert nach dem Projekt, zu dem sie gehören.", + }, + ], + }, + features: [ { tagline: "Immer im Blick", @@ -112,6 +144,19 @@ export const de: Dictionary = { "Pills für die neueste npm-Version und Sicherheitswarnungen. Zeile aufklappen für den Schweregrad je Version", ], }, + { + tagline: "Mehr als node_modules", + heading: "Dein Docker-Speicher, gruppiert nach Projekt.", + body: "Docker sammelt still Gigabyte in Images, Volumes, Containern und Build-Cache an. Öffne den Docker-Tab, um alles gruppiert nach dem Projekt zu sehen, zu dem jede Ressource gehört, mit echten Logos, Größen und Nutzungs-Badges. Gib verwaiste Images, gestoppte Container und ungenutzte Volumes mit einem Klick frei, mit denselben sicheren Bestätigungen wie im Rest von TidyDisk.", + bullets: [ + "Images, Volumes, Container und Build-Cache, jeweils mit ihrer echten Größe", + <> + Ressourcen nach Projekt gruppiert, anhand von Compose-Labels + und Verwendungs-Links + , + "Sichere, typisierte Bestätigungen, bevor etwas dauerhaft gelöscht wird", + ], + }, ], grid: { @@ -163,8 +208,8 @@ export const de: Dictionary = { comingSoonPill: "Demnächst", comingSoonText: ( <> - npm-, yarn- und bun-Caches, plus projektspezifische Build-Ausgaben - wie .next und dist. + Als Nächstes: npm-, yarn- und bun-Caches, plus projektspezifische + Build-Ausgaben wie .next und dist. ), }, diff --git a/site/lib/i18n/dictionaries/en.tsx b/site/lib/i18n/dictionaries/en.tsx index ae6fe6f..e0968d6 100644 --- a/site/lib/i18n/dictionaries/en.tsx +++ b/site/lib/i18n/dictionaries/en.tsx @@ -9,7 +9,7 @@ export const en: Dictionary = { title: "TidyDisk: see what is eating your dev disk, reclaim it in one click", description: - "TidyDisk lives in your macOS menu bar and shows what your dev projects really cost: every node_modules folder, your pnpm store, and every installed package. Free to scan. One-click cleanup with a 19 euro lifetime license. Safely, to the Trash, never rm -rf.", + "TidyDisk lives in your macOS menu bar and shows what your dev projects really cost: every node_modules folder, your pnpm store and package-manager caches, Docker images and volumes, and every installed package. Free to scan. One-click cleanup with a 19 euro lifetime license. Safely, to the Trash, never rm -rf.", blogTitle: "TidyDisk blog: keeping dev disks clean", blogDescription: "Practical guides on node_modules cleanup, package manager disk usage, and reclaiming space on a developer Mac. From the makers of TidyDisk.", @@ -38,10 +38,10 @@ export const en: Dictionary = { ), body: ( <> - Dev work quietly eats your Mac: old projects, heavy dependencies, - forgotten experiments. TidyDisk watches from the menu bar and gives - the space back in one click. Safely, to the Trash, never{" "} - rm -rf. + Dev work quietly eats your Mac: heavy node_modules, Docker + images, build caches, forgotten experiments. TidyDisk watches from the + menu bar and gives the space back in one click. Safely, to the Trash, + never rm -rf. ), downloadCta: "Download for macOS", @@ -52,12 +52,42 @@ export const en: Dictionary = { band: { statement: ( <> - node_modules is the heaviest object in the known - universe. We help you delete it. + node_modules is the heaviest object in the known universe. + But it is not alone: Docker images, build caches and dead projects pile + up too. TidyDisk clears all of it. ), }, + areas: { + kicker: "One tool, every kind of dev junk", + heading: ( + <> + Four places your disk quietly{" "} + fills up. + + ), + lead: "TidyDisk watches all of them and gives the space back, safely.", + cards: [ + { + title: "Projects", + copy: "Heavy, stale node_modules ranked by the real bytes you would free.", + }, + { + title: "Caches", + copy: "Your pnpm store and Docker build cache, pruned in one safe click.", + }, + { + title: "Packages", + copy: "A machine-wide dependency inventory: version drift, duplicates, and security advisories.", + }, + { + title: "Docker", + copy: "Images, volumes, containers and build cache, grouped by the project they belong to.", + }, + ], + }, + features: [ { tagline: "Always watching", @@ -113,6 +143,19 @@ export const en: Dictionary = { "Latest-on-npm and security-advisory pills. Expand a row for per-version severity", ], }, + { + tagline: "Beyond node_modules", + heading: "Your Docker disk, grouped by project.", + body: "Docker quietly hoards gigabytes in images, volumes, containers and build cache. Open the Docker tab to see it grouped by the project each resource belongs to, with real logos, sizes and in-use badges. Reclaim dangling images, stopped containers and unused volumes in a click, with the same safe confirmations as the rest of TidyDisk.", + bullets: [ + "Images, volumes, containers and build cache, each with its real size", + <> + Resources grouped by project from Compose labels and used-by + links + , + "Safe, typed confirmations before anything is permanently removed", + ], + }, ], grid: { @@ -164,8 +207,8 @@ export const en: Dictionary = { comingSoonPill: "Coming soon", comingSoonText: ( <> - npm, yarn & bun caches, plus per-project build outputs like{" "} - .next and dist. + Next up: npm, yarn & bun caches, plus per-project build outputs + like .next and dist. ), }, diff --git a/site/lib/i18n/dictionaries/es.tsx b/site/lib/i18n/dictionaries/es.tsx index 321ad9f..061bd42 100644 --- a/site/lib/i18n/dictionaries/es.tsx +++ b/site/lib/i18n/dictionaries/es.tsx @@ -7,7 +7,7 @@ export const es: Dictionary = { title: "TidyDisk: descubre qué ocupa el disco de tu Mac de desarrollo y recupéralo con un clic", description: - "TidyDisk vive en la barra de menús de macOS y te muestra el coste real de tus proyectos: cada carpeta node_modules, tu almacén de pnpm y todos los paquetes instalados. Escaneo gratis. Limpieza con un clic con licencia de por vida por 19 euros. De forma segura, a la Papelera, nunca con rm -rf.", + "TidyDisk vive en la barra de menús de macOS y te muestra el coste real de tus proyectos: cada carpeta node_modules, tu almacén de pnpm y las cachés de los gestores de paquetes, las imágenes y volúmenes de Docker, y todos los paquetes instalados. Escaneo gratis. Limpieza con un clic con licencia de por vida por 19 euros. De forma segura, a la Papelera, nunca con rm -rf.", blogTitle: "Blog de TidyDisk: mantén limpio el disco de desarrollo", blogDescription: "Guías prácticas sobre limpieza de node_modules, uso de disco de gestores de paquetes y cómo recuperar espacio en un Mac de desarrollador. De los creadores de TidyDisk.", @@ -36,11 +36,11 @@ export const es: Dictionary = { ), body: ( <> - El trabajo de desarrollo devora tu Mac en silencio: proyectos - antiguos, dependencias pesadas, experimentos olvidados. TidyDisk - vigila desde la barra de menús y te devuelve el espacio con un - clic. De forma segura, a la Papelera, nunca con{" "} - rm -rf. + El trabajo de desarrollo devora tu Mac en silencio:{" "} + node_modules pesados, imágenes Docker, cachés de + compilación, experimentos olvidados. TidyDisk vigila desde la + barra de menús y te devuelve el espacio con un clic. De forma + segura, a la Papelera, nunca con rm -rf. ), downloadCta: "Descargar para macOS", @@ -52,11 +52,42 @@ export const es: Dictionary = { statement: ( <> node_modules es el objeto más pesado del universo - conocido. Te ayudamos a borrarlo. + conocido. Pero no está solo: imágenes Docker, cachés de + compilación y proyectos muertos también se acumulan.{" "} + TidyDisk lo limpia todo. ), }, + areas: { + kicker: "Una sola herramienta, todo tipo de basura de desarrollo", + heading: ( + <> + Cuatro lugares donde tu disco se llena{" "} + en silencio. + + ), + lead: "TidyDisk vigila los cuatro y te devuelve el espacio, de forma segura.", + cards: [ + { + title: "Proyectos", + copy: "node_modules pesados y obsoletos, ordenados por los bytes reales que liberarías.", + }, + { + title: "Cachés", + copy: "Tu almacén de pnpm y la caché de compilación de Docker, podados con un clic seguro.", + }, + { + title: "Paquetes", + copy: "Un inventario de dependencias de todo el equipo: versiones dispersas, duplicados y avisos de seguridad.", + }, + { + title: "Docker", + copy: "Imágenes, volúmenes, contenedores y caché de compilación, agrupados por el proyecto al que pertenecen.", + }, + ], + }, + features: [ { tagline: "Siempre vigilando", @@ -112,6 +143,19 @@ export const es: Dictionary = { "Etiquetas de última versión en npm y avisos de seguridad. Despliega una fila para ver la gravedad por versión", ], }, + { + tagline: "Más allá de node_modules", + heading: "Tu espacio en Docker, agrupado por proyecto.", + body: "Docker acumula gigabytes en silencio en imágenes, volúmenes, contenedores y caché de compilación. Abre la pestaña Docker para verlo agrupado por el proyecto al que pertenece cada recurso, con logotipos reales, tamaños e insignias de uso. Recupera imágenes huérfanas, contenedores detenidos y volúmenes sin usar con un clic, con las mismas confirmaciones seguras que el resto de TidyDisk.", + bullets: [ + "Imágenes, volúmenes, contenedores y caché de compilación, cada uno con su tamaño real", + <> + Recursos agrupados por proyecto a partir de las etiquetas de + Compose y los enlaces de uso + , + "Confirmaciones seguras y tipadas antes de eliminar algo de forma permanente", + ], + }, ], grid: { @@ -163,8 +207,8 @@ export const es: Dictionary = { comingSoonPill: "Próximamente", comingSoonText: ( <> - Cachés de npm, yarn y bun, además de artefactos de compilación - por proyecto como .next y dist. + Lo próximo: cachés de npm, yarn y bun, además de artefactos de + compilación por proyecto como .next y dist. ), }, diff --git a/site/lib/i18n/dictionaries/fr.tsx b/site/lib/i18n/dictionaries/fr.tsx index b0b4364..853d9d4 100644 --- a/site/lib/i18n/dictionaries/fr.tsx +++ b/site/lib/i18n/dictionaries/fr.tsx @@ -9,7 +9,7 @@ export const fr: Dictionary = { title: "TidyDisk : voyez ce qui dévore le disque de vos projets, récupérez-le en un clic", description: - "TidyDisk révèle ce que vos projets de dev coûtent : node_modules, store pnpm, packages installés. Scan gratuit, nettoyage en un clic, à vie pour 19 euros.", + "TidyDisk vit dans la barre de menu macOS et révèle ce que vos projets de dev coûtent vraiment : chaque dossier node_modules, votre store pnpm et les caches des gestionnaires de paquets, les images et volumes Docker, et chaque package installé. Scan gratuit. Nettoyage en un clic avec une licence à vie à 19 euros. En toute sécurité, vers la Corbeille, jamais rm -rf.", blogTitle: "Le blog TidyDisk : garder un disque de dev propre", blogDescription: "Guides pratiques sur le nettoyage de node_modules, l'usage disque des gestionnaires de paquets, et la récupération d'espace sur un Mac de développeur.", @@ -37,10 +37,11 @@ export const fr: Dictionary = { ), body: ( <> - Le travail de dev grignote votre Mac en silence : vieux projets, - dépendances lourdes, expériences oubliées. TidyDisk veille depuis la - barre de menu et vous rend l’espace en un clic. En toute sécurité, - vers la Corbeille, jamais rm -rf. + Le travail de dev grignote votre Mac en silence :{" "} + node_modules lourds, images Docker, caches de build, + expériences oubliées. TidyDisk veille depuis la barre de menu et vous + rend l’espace en un clic. En toute sécurité, vers la Corbeille, + jamais rm -rf. ), downloadCta: "Télécharger pour macOS", @@ -52,11 +53,42 @@ export const fr: Dictionary = { statement: ( <> node_modules est l’objet le plus lourd de l’univers - connu. On vous aide à le supprimer. + connu. Mais il n’est pas seul : images Docker, caches de build et + projets morts s’accumulent aussi.{" "} + TidyDisk nettoie tout ça. ), }, + areas: { + kicker: "Un seul outil, tout le bazar du dev", + heading: ( + <> + Quatre endroits où votre disque se remplit{" "} + en silence. + + ), + lead: "TidyDisk surveille les quatre et vous rend l’espace, en toute sécurité.", + cards: [ + { + title: "Projects", + copy: "De lourds node_modules obsolètes, classés par les octets réels que vous libéreriez.", + }, + { + title: "Caches", + copy: "Votre store pnpm et le cache de build Docker, purgés en un clic sans risque.", + }, + { + title: "Packages", + copy: "Un inventaire des dépendances à l'échelle de la machine : dérive de versions, doublons et alertes de sécurité.", + }, + { + title: "Docker", + copy: "Images, volumes, conteneurs et cache de build, regroupés par le projet auquel ils appartiennent.", + }, + ], + }, + features: [ { tagline: "Toujours en veille", @@ -114,6 +146,19 @@ export const fr: Dictionary = { "Pastilles dernière version npm et alerte de sécurité. Développez une ligne pour la sévérité par version", ], }, + { + tagline: "Au-delà de node_modules", + heading: "Votre espace Docker, regroupé par projet.", + body: "Docker accumule discrètement des gigaoctets dans les images, volumes, conteneurs et le cache de build. Ouvrez l'onglet Docker pour le voir regroupé par le projet auquel appartient chaque ressource, avec les vrais logos, tailles et badges d’utilisation. Récupérez les images orphelines, les conteneurs arrêtés et les volumes inutilisés en un clic, avec les mêmes confirmations sécurisées que le reste de TidyDisk.", + bullets: [ + "Images, volumes, conteneurs et cache de build, chacun avec sa taille réelle", + <> + Ressources regroupées par projet à partir des labels Compose + et des liens d’utilisation + , + "Confirmations sûres et typées avant toute suppression définitive", + ], + }, ], grid: { @@ -147,7 +192,7 @@ export const fr: Dictionary = { }, { title: "Jauge disque en pixels", - copy: "Une barre visible en un regard qui se remplit et rougit à mesure que vos dépendances s'accumulent.", + copy: "Une barre visible en un regard qui se remplit et rougit à mesure que vos dépendances s’accumulent.", }, { title: "Révéler dans le Finder", @@ -165,8 +210,8 @@ export const fr: Dictionary = { comingSoonPill: "Bientôt disponible", comingSoonText: ( <> - Les caches npm, yarn & bun, plus les sorties de build par projet - comme .next et dist. + À venir : les caches npm, yarn & bun, plus les sorties de build + par projet comme .next et dist. ), }, diff --git a/site/lib/i18n/dictionaries/pt.tsx b/site/lib/i18n/dictionaries/pt.tsx index 528f4db..a990334 100644 --- a/site/lib/i18n/dictionaries/pt.tsx +++ b/site/lib/i18n/dictionaries/pt.tsx @@ -9,7 +9,7 @@ export const pt: Dictionary = { title: "TidyDisk: veja o que está consumindo o disco do seu ambiente de dev, recupere-o em um clique", description: - "O TidyDisk mostra o que seus projetos de dev custam no disco: node_modules, pnpm store e pacotes instalados. Grátis para escanear, limpeza por 19 euros.", + "O TidyDisk vive na barra de menu do macOS e mostra o que seus projetos de dev realmente custam: cada pasta node_modules, seu pnpm store e os caches dos gerenciadores de pacotes, imagens e volumes Docker, e cada pacote instalado. Grátis para escanear. Limpeza em um clique com licença vitalícia por 19 euros. Com segurança, para a Lixeira, nunca rm -rf.", blogTitle: "Blog do TidyDisk: mantendo discos de dev limpos", blogDescription: "Guias práticos sobre limpeza de node_modules, uso de disco por gerenciadores de pacotes e como recuperar espaço em um Mac de desenvolvedor. Da equipe do TidyDisk.", @@ -38,10 +38,11 @@ export const pt: Dictionary = { ), body: ( <> - O trabalho de dev consome seu Mac silenciosamente: projetos antigos, - dependências pesadas, experimentos esquecidos. O TidyDisk observa a - partir da barra de menu e devolve o espaço em um clique. Com - segurança, para a Lixeira, nunca rm -rf. + O trabalho de dev consome seu Mac silenciosamente:{" "} + node_modules pesados, imagens Docker, caches de build, + experimentos esquecidos. O TidyDisk observa a partir da barra de + menu e devolve o espaço em um clique. Com segurança, para a + Lixeira, nunca rm -rf. ), downloadCta: "Baixar para macOS", @@ -53,11 +54,42 @@ export const pt: Dictionary = { statement: ( <> node_modules é o objeto mais pesado do universo - conhecido. Nós ajudamos você a apagá-lo. + conhecido. Mas ele não está sozinho: imagens Docker, caches de + build e projetos mortos também se acumulam.{" "} + TidyDisk limpa tudo isso. ), }, + areas: { + kicker: "Uma única ferramenta para toda a bagunça de dev", + heading: ( + <> + Quatro lugares onde seu disco se enche{" "} + em silêncio. + + ), + lead: "O TidyDisk vigia todos eles e devolve o espaço, com segurança.", + cards: [ + { + title: "Projects", + copy: "node_modules pesados e antigos, classificados pelos bytes reais que você liberaria.", + }, + { + title: "Caches", + copy: "Seu pnpm store e o cache de build do Docker, limpos com um clique seguro.", + }, + { + title: "Pacotes", + copy: "Um inventário de dependências de todo o computador: dispersão de versões, duplicatas e alertas de segurança.", + }, + { + title: "Docker", + copy: "Imagens, volumes, containers e cache de build, agrupados pelo projeto ao qual pertencem.", + }, + ], + }, + features: [ { tagline: "Sempre vigiando", @@ -112,6 +144,19 @@ export const pt: Dictionary = { "Selos de mais recente no npm e alerta de segurança. Expanda uma linha para severidade por versão", ], }, + { + tagline: "Além do node_modules", + heading: "Seu espaço em Docker, agrupado por projeto.", + body: "O Docker acumula gigabytes silenciosamente em imagens, volumes, containers e cache de build. Abra a aba Docker para ver tudo agrupado pelo projeto ao qual cada recurso pertence, com logotipos reais, tamanhos e selos de uso. Recupere imagens órfãs, containers parados e volumes não usados em um clique, com as mesmas confirmações seguras do resto do TidyDisk.", + bullets: [ + "Imagens, volumes, containers e cache de build, cada um com seu tamanho real", + <> + Recursos agrupados por projeto a partir dos labels do Compose + e dos vínculos de uso + , + "Confirmações seguras e tipadas antes de qualquer remoção permanente", + ], + }, ], grid: { @@ -163,8 +208,8 @@ export const pt: Dictionary = { comingSoonPill: "Em breve", comingSoonText: ( <> - Caches de npm, yarn e bun, além de artefatos de build por projeto - como .next e dist. + A seguir: caches de npm, yarn e bun, além de artefatos de build + por projeto como .next e dist. ), }, diff --git a/site/lib/i18n/i18n.types.ts b/site/lib/i18n/i18n.types.ts index e5aacfe..7701533 100644 --- a/site/lib/i18n/i18n.types.ts +++ b/site/lib/i18n/i18n.types.ts @@ -198,13 +198,29 @@ export interface BlogChromeCopy { ctaButton: string; } +/** One card in the four-up "areas" section (icon + accent flag stay in the component). */ +export interface AreaCardCopy { + title: string; + copy: string; +} + +export interface AreasCopy { + kicker: string; + /** Accent `` around the last words. */ + heading: ReactNode; + lead: string; + /** Fixed order: Projects, Caches, Packages, Docker. */ + cards: [AreaCardCopy, AreaCardCopy, AreaCardCopy, AreaCardCopy]; +} + export interface Dictionary { meta: MetaCopy; nav: NavCopy; hero: HeroCopy; band: BandCopy; - /** Exactly four feature rows, in display order. */ - features: [FeatureCopy, FeatureCopy, FeatureCopy, FeatureCopy]; + areas: AreasCopy; + /** Exactly five feature rows, in display order (row 5 is Docker). */ + features: [FeatureCopy, FeatureCopy, FeatureCopy, FeatureCopy, FeatureCopy]; grid: GridCopy; why: WhyCopy; how: HowCopy;