Skip to content

site: drop the legacy desktop download CTA and unused platform-detect code - #396

Merged
mohabbis merged 3 commits into
masterfrom
site/remove-legacy-download-cta
Aug 4, 2026
Merged

site: drop the legacy desktop download CTA and unused platform-detect code#396
mohabbis merged 3 commits into
masterfrom
site/remove-legacy-download-cta

Conversation

@mohabbis

@mohabbis mohabbis commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • Removes the "Desktop preview downloads" section (dl-mac/dl-win buttons for the superseded v2.0.3 Tauri app) from the marketing site — a prominent download CTA for a product that isn't the one being built or sold anymore. The one honest fact (v2.0.3 is archived on GitHub releases) is folded into the existing legacy blurb as a link instead of a button row.
  • Removes now-dead code: platformDetection() in main.js, the CSS/markup it drove (#download-sub, data-download-label, dl-mac/dl-win platform dimming, data-platform body attribute), the unused i-download/i-apple/i-windows/i-layers SVG symbol defs, and the .pill utility (its only consumer was the removed pill).
  • Fixes a garbled sentence in the About section ("Direction will not: approval-gated execution...") that was missing a verb.
  • Removes deploy-website.yml's "Validate download links" step, which hard-required Ghost.dmg/Ghost_Setup.exe links in the markup and would otherwise fail every future deploy once those links are gone.
  • Drops honesty-signaling asides that read as meta-commentary rather than product claims: "no fake logos" (hero note), "Not a fake customer page" (positioning row), the "Not built yet" proof-list item, and the About section's "Working name: Ghost — not changing it yet" note.

Test plan

  • <section>/<li> tags balanced.
  • JSON-LD blocks still parse as valid JSON.
  • node --check public/main.js passes.
  • Confirmed no remaining references to removed ids/classes/phrases.

🤖 Generated with Claude Code

mohabbis and others added 2 commits August 3, 2026 18:30
cloud.yml was activated in PR #393, but the handoff doc still told the
next reader "cloud/ has no CI" and to validate everything locally.
Update both docs to reflect that CI is live, path-scoped, and runs
against real Postgres/Redis, and correct the stale "expect 220 tests"
count to the current 239.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… code

The marketing site is cloud-first now, but still ran a full "Desktop
preview downloads" section with dl-mac/dl-win buttons for the
superseded v2.0.3 Tauri app — a prominent CTA for a product that
isn't what's being built or sold. Remove it; fold the one honest fact
(v2.0.3 is archived on GitHub releases) into the existing legacy
blurb as a link instead of a button row.

Also:
- drop platformDetection() in main.js and the CSS/markup it drove
  (#download-sub, data-download-label, dl-mac/dl-win platform
  dimming, data-platform attribute) — all dead once the section is
  gone.
- drop the now-unused i-download/i-apple/i-windows/i-layers SVG
  symbol defs and the .pill utility (its only consumer was the
  removed download pill).
- fix a garbled sentence in the About section ("Direction will not:
  approval-gated execution...") that was missing a verb; it now
  reads as intended and states plainly that the name isn't changing
  yet.
- drop deploy-website.yml's "Validate download links" step, which
  hard-required Ghost.dmg/Ghost_Setup.exe links in the markup and
  would otherwise fail every future deploy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ghost Ready Ready Preview Aug 3, 2026 11:43pm

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Removes "no fake logos" (hero note), "Not a fake customer page"
(positioning row), the "Not built yet" proof-list item, and the
About section's "Working name: Ghost — not changing it yet" note.
These were meta-commentary about the site's own honesty rather than
claims about the product; the remaining content (What's real / Known
limits sections) already carries the substantive proof.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mohabbis
mohabbis merged commit 78665ad into master Aug 4, 2026
25 checks passed
mohabbis added a commit that referenced this pull request Aug 5, 2026
The version-consistency check hard-failed when public/index.html didn't
contain a vX.Y.Z string, cross-checked against README.md. That assumption
broke on master after the site copy was rewritten to drop legacy desktop
positioning entirely (following the download-CTA removal in #396) -- the
site no longer mentions a specific release at all, which is the intended
current state, not drift.

README.md still names the published desktop tag as historical reference;
nothing there needs to change. Only cross-check the two when the site
actually advertises a version, so a real future mismatch still fails loud.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
mohabbis added a commit that referenced this pull request Aug 5, 2026
* feat(cloud): browser recording capture — Chrome extension + deterministic compile

Priority 3. Ghost could execute and approve, but nobody could record — the
only way to get a workflow into Ghost was to upload a trace file produced by
some other tool. There was no capture at all, and `RecordingStatus.ACTIVE` was
never set because nothing recorded.

Per the capture decision in docs/ARCHITECTURE_DECISIONS.md §3, this ships a
Chrome extension for v1, execution stays entirely server-side.

## The trace contract (`@ghost/core/recording/trace`)

A Zod schema for what a recorder uploads: navigate/click/input/select/submit
events, each carrying a `TraceTarget` with accessible role, name, test id and
ordered CSS fallbacks. The defining property: **role and name are read at
capture time**, off the live DOM while the element is still on screen — not
inferred later from an opaque trace. Those are exactly the fields
`resolveLocator` already prefers, so nothing downstream had to change.

A secret is never captured, not encrypted or truncated — absent. `redacted:
true` records that something was typed without recording what. This is the
only place that redaction can be done honestly: a trace carrying the value
plus a "please ignore this" flag has already leaked it (see the P0-2 finding
in the architecture doc, about the previous design uploading traces whole to
a third party).

## The compiler (`@ghost/core/recording/compile`)

Trace to typed steps, deterministically. No model, no network, no configured
compiler. Three things it does beyond translation:

  - collapses a run of keystrokes on one field into one `fill` with the final
    value
  - runs every produced step through `classifyStep` — the same deterministic
    classifier the worker consults at run time — and inserts an `approval`
    immediately before anything it would gate, so the authored workflow
    agrees with what execution will actually do
  - never carries a redacted value; a secret field becomes a `fill` marked
    `sensitive` with a placeholder, and a note tells the reviewer to set it

This is what makes recording work with no compiler configured — the state
production runs in per the HarnessRouter decision (§1). A model still has a
job: naming steps, proposing gates beyond the obvious cases, flagging what it
could not resolve. Deciding which element was clicked is not a judgement call
and should never have been one.

24 tests, including a fixture shaped exactly like the extension's actual
output (`roundtrip.test.ts`) — the seam most likely to rot, since the
extension is untyped JS with nothing else to notice if its output drifts from
what the compiler expects.

## Ingest (`lib/recording-ingest.ts`)

Shared by the existing upload form and the extension, so upload limits,
filename sanitisation and audit events cannot drift between the two paths. A
structured Ghost trace compiles inline and lands `READY` in the same request;
anything else (HAR, Playwright zip) is stored and left for whatever compiler
is configured, unchanged from before.

`POST /api/agent/recordings` is new: the extension's ingest point, on the
already bearer-authenticated agent surface rather than the session-only
upload route, since the extension runs at a different origin and cannot rely
on the session cookie reaching it. `resolveAgentPrincipal` enforces the same
second factor on its session fallback as every other agent route (see the
P0-1 fix). Uploading only creates a proposal — nothing here publishes a
workflow or executes anything; the human still reviews compiled steps in the
editor and publishes through `POST /api/workflows`, which revalidates them.

## The extension (`apps/extension`)

Manifest V3. `content.js` captures clicks, typing, selects, submits and SPA
navigation, computing accessible name in roughly accname-spec order from
label/aria-label/aria-labelledby/placeholder/value/text. Secret fields are
detected by `type=password`, secret-shaped `autocomplete`, or a name/label/id
matching a word list (password, otp, cvv, card number, ssn, ...) — and the
value is never read for them, not merely withheld after reading.
`background.js` buffers events locally and uploads only on Stop, via a
revocable bearer token created in Ghost Settings. The popup is deliberately
thin — start, stop, where to send it — because a second place to edit steps
would be a second thing to keep in sync with the real editor.

## Validation

398 tests pass (up from 374 — 24 new), against a database migrated from
zero. `pnpm typecheck` and `pnpm build` both clean with `HR_API_KEY` unset.
Manually verified `ingestTrace` end-to-end against a real Postgres: a
structured trace lands `compileStatus: READY` with steps persisted, in one
request, no compiler configured.

## What is not yet covered

`GET /api/recordings` gained a `take: 50` (P2-2 for this route) as an
incidental fix while touching the file; the rest of that finding stands.
Extension host permissions are `<all_urls>` for v1; scoping to allowlisted
origins per organization is a natural follow-up once there is a customer to
scope it for. No component tests for the extension itself — Manifest V3
content scripts have no test harness in this repo, so `roundtrip.test.ts`
pinning its exact output shape is the coverage that exists.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(worker): make production container build and boot reliably

The worker Dockerfile had never actually been built or run since it was
written. Auditing what was genuinely left on the cloud roadmap surfaced two
stacked bugs, neither caught by CI (`pnpm build` only bundles the worker with
tsup at the workspace level — it never exercises the Dockerfile's own COPY
list or executes the resulting dist/index.js):

- The deps stage copied packages/core/package.json but not its prisma/
  directory. @ghost/core's postinstall runs a bare `prisma generate`, which
  resolves the schema at the default ./prisma/schema.prisma path, so
  `pnpm install` failed inside the image before the build stage (which does
  copy the rest of packages/core) ever ran. The same gap silently dropped
  tsconfig.base.json, breaking apps/worker/tsconfig.json's `extends`.

- Once building, the container crashed immediately on boot: bundling
  @ghost/core (via tsup's `noExternal: [/^@ghost\//]`) pulls in
  @prisma/client's generated CJS runtime, which dynamically requires native
  query-engine files — esbuild's CJS-to-ESM interop can't represent that and
  throws "Dynamic require of 'fs' is not supported" at the first call. Fixed
  by keeping @prisma/client external in tsup.config.ts and adding it as a
  direct dependency of @ghost/worker, since pnpm's strict linking won't
  resolve a transitive dep at the worker's own require path otherwise.

Verified by rebuilding the image and running it against real Postgres/Redis
until it logged its startup line rather than crashing. Added a CI step that
builds and boot-smoke-tests the image on every PR so this class of bug can't
ship invisibly again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs(cloud): reconcile handoff and status documentation with current implementation

CURSOR_HANDOFF.md described the WorkflowCompiler abstraction, four-eyes
approval, and S3 artifact serving as open work, framing a stale roadmap that
led to prioritizing already-shipped items. All three were fully implemented
and tested by earlier PRs (#403, #406) whose changes never made it back into
this file's "remaining work" section:

- WorkflowCompiler: apps/web/src/lib/compiler/{types,index,harness-router-compiler}.ts
  already provides the interface, a swappable HarnessRouter adapter, and
  normalized error types.
- Four-eyes approval: Membership/Role/Invitation plus
  Organization.requireSeparateApprover already enforce requester != approver
  server-side, covered by separation-of-duties.test.ts.
- S3 artifact serving: packages/core/src/storage/artifacts.ts already has a
  working S3ArtifactStore with presigned URLs; only retention/cleanup is
  genuinely still open, called out as such.

Also documents the worker container bugs found and fixed in the prior commit,
and corrects the stale "239 tests" figure (actual full green run is 398) and
the "typed step editor: remaining" status line (workflow-editor.tsx already
covers it) in both this file and README.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(web): stop signin from silently rendering with no way to sign in

In production with no GitHub OAuth app configured, the signin page rendered
a card with a title and zero buttons -- indistinguishable from a bug. Whoever
hits this is more likely to be standing up the deployment than an end user,
so name the exact fix (AUTH_GITHUB_ID/AUTH_GITHUB_SECRET, the callback URL)
instead of failing silently. See docs/DEPLOY.md's "sign-in trap".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat(worker): add artifact retention, structured logging, and opt-in Sentry error tracking

Three gaps identified by an audit of what was genuinely left on the cloud
roadmap (recorded in CURSOR_HANDOFF.md), closed as one changeset since they
share the same few files (index.ts, purgeArtifacts.ts):

- Retention: nothing ever deleted a run's screenshots. `purge-artifacts` is a
  new BullMQ job, scheduled daily via upsertJobScheduler at worker boot, that
  deletes a run's artifact prefix once it ended more than
  ARTIFACT_RETENTION_DAYS ago (default 90) and audits the deletion. Run.
  artifactsPurgedAt makes it idempotent; a store failure is retried next cycle
  rather than silently marked done. Eligibility is a pure function
  (packages/core/src/retention.ts) so the window logic is tested without a
  database.

- Structured logging: the worker only had console.log/console.error, so a
  failure was invisible unless someone was tailing container logs.
  packages/core/src/logger.ts is a small, dependency-free JSON-line logger
  (errors to stderr, everything else to stdout) now used throughout
  index.ts and purgeArtifacts.ts.

- Error tracking: @ghost/core/sentry wraps @sentry/node, gated on SENTRY_DSN
  exactly like HR_API_KEY/S3_BUCKET -- absent means a complete no-op, present
  enables capture on every job-failure handler. Worker-only: wiring apps/web
  needs @sentry/nextjs (its own webpack/turbopack plugin exists specifically
  to handle Sentry's auto-instrumentation, which cannot otherwise be bundled --
  confirmed by trying the manual approach first and watching it break
  `pnpm build` with an unbundleable node:child_process import). @sentry/node
  gets the same tsup `external` treatment as @prisma/client, for the same
  reason: its runtime does dynamic requires that bundling breaks.

Verified end-to-end: full test suite (424 tests) against real Postgres +
Redis, a Docker rebuild, and a boot smoke test with SENTRY_DSN actually set
(not just absent) to confirm Sentry initializing doesn't crash the container.

DEPLOY.md, README.md and CURSOR_HANDOFF.md updated for the new env vars and
the corrected test count.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(cloud): make cloud/apps/web actually deployable to Vercel

Discovered by performing the repo's first real deployment (Vercel project
ghost-app, Neon Postgres, Upstash Redis) rather than just writing a checklist
for one:

- cloud/apps/web needs its own vercel.json ({"framework": "nextjs"}).
  DEPLOY.md previously claimed this wasn't necessary based on reading Vercel's
  monorepo docs, but empirically: with the repo root's own vercel.json present
  (the marketing site's static-build config) and Root Directory set but no
  local vercel.json, a build falls back to the root's install/build commands
  against the wrong project's uploaded files -- Vercel prints "The vercel.json
  file should be inside of the provided root directory" as a warning, then
  uses it anyway. A local vercel.json closes the gap outright.
- Added .vercelignore at the repo root. `vercel deploy` uploads the working
  directory as-is, not `git ls-files`, so untracked local dev artifacts
  (.worktrees/, .wt/, .turbo/ -- each a separate git-worktree checkout with
  its own node_modules/target) get swept into the upload. Without this, a
  deploy from the repo root uploaded 57,896 files instead of ~700.
- Vercel's own Deployment Protection (SSO/Vercel Authentication) was on by
  default for the new project, gating every page behind a Vercel-account
  login wall on top of Ghost's own auth -- disabled for ghost-app.

DEPLOY.md updated with all three findings, plus removing the stale "no
deployment exists yet" framing now that apps/web is live (worker is not; no
container host is wired up, and object storage was deliberately deferred).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* ci: stop requiring the marketing site to advertise a desktop release

The version-consistency check hard-failed when public/index.html didn't
contain a vX.Y.Z string, cross-checked against README.md. That assumption
broke on master after the site copy was rewritten to drop legacy desktop
positioning entirely (following the download-CTA removal in #396) -- the
site no longer mentions a specific release at all, which is the intended
current state, not drift.

README.md still names the published desktop tag as historical reference;
nothing there needs to change. Only cross-check the two when the site
actually advertises a version, so a real future mismatch still fails loud.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant