From 37897f07c32c20fb08bcc125c26200524185314c Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sat, 25 Apr 2026 12:41:59 +0200 Subject: [PATCH 1/9] chore: remove all nJAMS references from the live site MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per Hendrik's direction, no nJAMS references on the website. The founder pedigree claim now stands on the years and the customer segment ("15 years building integration monitoring for banks, insurers, and logistics operators") without naming a prior product. Changes: - src/components/sections/SocialProofStrip.astro: drop the ' · ex-nJAMS' suffix from the founder attribution; collapse the multi-step PENDING comment into a single-line founder-name TODO. - src/components/sections/WhyUs.astro: drop the trademark-review comment (no longer relevant — the body text never named the prior product, only the comment did). - OPERATOR-CHECKLIST.md: remove the 'Why us / nJAMS wording review' pre-publish task. Also rename the adjacent 'MID-tier retention' TODO to 'Starter-tier retention' to match the relaunched tier taxonomy. - docs/superpowers/specs/2026-04-25-cameleer-website-relaunch-design.md: update §4, §6.2, §12 to reflect the removed wording. Only the founder-name placeholder remains as a pre-publish blocker. Historical specs / plans under docs/superpowers/{specs,plans}/ keep their original wording — they're records of past decisions and are not on the website. Co-Authored-By: Claude Opus 4.7 (1M context) --- OPERATOR-CHECKLIST.md | 3 +-- .../2026-04-25-cameleer-website-relaunch-design.md | 12 ++++-------- src/components/sections/SocialProofStrip.astro | 9 ++------- src/components/sections/WhyUs.astro | 2 -- 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/OPERATOR-CHECKLIST.md b/OPERATOR-CHECKLIST.md index cadb4ce..16f29a2 100644 --- a/OPERATOR-CHECKLIST.md +++ b/OPERATOR-CHECKLIST.md @@ -87,8 +87,7 @@ workflows read them via the `${{ secrets.* }}` context. - [ ] Fill in `src/pages/imprint.astro` `operator` object with real legal details. - [ ] Fill in `operatorContact` in `src/pages/privacy.astro`. -- [ ] Review the "Why us" / nJAMS wording in `src/components/sections/WhyUs.astro` for trademark safety. -- [ ] Confirm MID-tier retention: spec says **7 days**; `cameleer-saas/HOWTO.md` says **30 days**. Reconcile one side or the other. +- [ ] Confirm Starter-tier retention: spec says **7 days**; `cameleer-saas/HOWTO.md` says **30 days**. Reconcile one side or the other. ## 5. First deploy diff --git a/docs/superpowers/specs/2026-04-25-cameleer-website-relaunch-design.md b/docs/superpowers/specs/2026-04-25-cameleer-website-relaunch-design.md index 2446edd..f5f497b 100644 --- a/docs/superpowers/specs/2026-04-25-cameleer-website-relaunch-design.md +++ b/docs/superpowers/specs/2026-04-25-cameleer-website-relaunch-design.md @@ -53,7 +53,7 @@ The relaunch's H1 leans Manager-outcome on purpose — the IT Manager is the che The original site shipped with no social proof. This relaunch can't fix that with logos or attributed customer quotes — **none are ready to publish**. The two anchors we *can* lean on: -1. **Founder pedigree** (nJAMS lineage). Subject to trademark clearance — same gating as `WhyUs.astro`'s existing comment. Until cleared, the wording is held in a `` HTML comment. +1. **Founder pedigree** — "15 years building integration monitoring for banks, insurers, and logistics operators." No prior-product name is used. The pedigree claim stands on the years and the customer-segment, not on a brand reference. 2. **Design-partner program**. Reframes the pre-customer state as a feature ("hand-picked early partners"), with a `mailto:` CTA to `PUBLIC_SALES_EMAIL` that the visitor can use to apply. Both anchors live in a new dedicated **Social Proof Strip** section (§6.2) immediately below the hero. @@ -118,14 +118,12 @@ Order is deliberate — see §11 for the rationale (proof-first arc: hero → wh - **Eyebrow** (mono, amber, ~12px): `// Built by people who've done this before` - **Quote block** (italic, ~17px, max 62ch, accent-colored 3px left border, padding-left ~20px): > *"We spent 15 years building integration monitoring for banks that couldn't afford downtime. Cameleer is what we'd build today — purpose-built for Apache Camel, no retrofit."* -- **Attribution** (~13px, muted, mono): `— [Founder Name], co-founder · ex-nJAMS` +- **Attribution** (~13px, muted, mono): `— [Founder Name], co-founder` - **Below attribution** (~24px gap, then a single `mailto:`-styled CTA in mono+cyan): `Apply to the design-partner program →` **`` gates** (do not ship without resolving): -- `[Founder Name]` is a placeholder. -- `ex-nJAMS` mention is gated on trademark clearance (same as `WhyUs.astro`'s existing §10 caveat). -- Either gate may be deferred by removing the affected line — the section still works as "pedigree quote, design-partner CTA" without the nJAMS-specific phrase. +- `[Founder Name]` is a placeholder. Must be filled in pre-publish. **Design-partner CTA target**: built inline in `SocialProofStrip.astro` using `auth.salesEmail` (not `auth.salesMailto`, which has no subject helper): @@ -372,15 +370,13 @@ A traditional SaaS layout (features → benefits → how → pricing) would put - [ ] `prefers-reduced-motion: reduce` disables the hero-mark sway and any tile-rise animations (existing handling preserved). - [ ] Tab focus order on the homepage is: nav → hero primary → hero secondary → social-proof CTA → walkthrough CTA targets … → final CTA. - [ ] All `mailto:` links open with the correct subject (design-partner CTA + sales contacts). -- [ ] Trademark `` gate in `SocialProofStrip.astro` and `WhyUs.astro` is reviewed before publish. - [ ] Founder name placeholder is filled in `SocialProofStrip.astro` before publish. **Pre-publish blockers** (recorded in code as `` HTML comments): 1. `[Founder Name]` placeholder in `SocialProofStrip.astro` — must be replaced with a real name. -2. nJAMS / `ex-nJAMS` wording — must clear trademark review (existing pattern from `WhyUs.astro`). -These are deliberately surfaced as code-level TODOs rather than spec-level open questions so the operator can't accidentally publish with the placeholders intact. +This is deliberately surfaced as a code-level TODO rather than a spec-level open question so the operator can't accidentally publish with the placeholder intact. --- diff --git a/src/components/sections/SocialProofStrip.astro b/src/components/sections/SocialProofStrip.astro index 4105290..4adec98 100644 --- a/src/components/sections/SocialProofStrip.astro +++ b/src/components/sections/SocialProofStrip.astro @@ -3,12 +3,7 @@ import { getAuthConfig } from '../../config/auth'; const auth = getAuthConfig(); -// PENDING — must be filled in before publish: -// 1. [Founder Name] placeholder below. -// 2. The "ex-nJAMS" mention is gated on Hendrik's trademark review -// (same pattern as WhyUs.astro §10 caveat). If the review is not -// cleared by publish time, drop the " · ex-nJAMS" suffix from the -// attribution line. +// PENDING — [Founder Name] placeholder must be filled in before publish. const founderName = '[Founder Name]'; const designPartnerSubject = 'Design partner enquiry — Cameleer'; const designPartnerHref = `mailto:${auth.salesEmail}?subject=${encodeURIComponent(designPartnerSubject)}`; @@ -24,7 +19,7 @@ const designPartnerHref = `mailto:${auth.salesEmail}?subject=${encodeURIComponen “We spent 15 years building integration monitoring for banks that couldn’t afford downtime. Cameleer is what we’d build today — purpose-built for Apache Camel, no retrofit.”

From 9b0c36b5e0cb489991919032d91754f041e326c1 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sat, 25 Apr 2026 17:57:41 +0200 Subject: [PATCH 2/9] docs(spec): add design for under-construction placeholder Branded "back shortly" page deployed via a new manual-trigger deploy-placeholder.yml workflow. Standalone HTML + two PNG siblings, no Astro build dependency, shares the deploy-production concurrency group with deploy.yml so the two can never race. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...5-under-construction-placeholder-design.md | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-25-under-construction-placeholder-design.md diff --git a/docs/superpowers/specs/2026-04-25-under-construction-placeholder-design.md b/docs/superpowers/specs/2026-04-25-under-construction-placeholder-design.md new file mode 100644 index 0000000..3b028d0 --- /dev/null +++ b/docs/superpowers/specs/2026-04-25-under-construction-placeholder-design.md @@ -0,0 +1,133 @@ +# Under-construction placeholder — design + +**Date:** 2026-04-25 +**Status:** approved (pending user spec review) + +## 1. Purpose + +A branded "back shortly" page for cameleer.io that the operator can swap into the live origin on demand from Gitea Actions. Used during planned maintenance, incident fallback, or any moment the real site needs to come down without leaving visitors on a broken page. + +## 2. Constraints & non-goals + +- **Same target as the real site.** Deploys to the same Hetzner Webhosting L docroot used by `deploy.yml`. Replaces the live `index.html` and assets at the docroot root. +- **Must work when the main build is broken.** This is the worst-case in which a placeholder is needed. Therefore the placeholder MUST NOT depend on `npm ci`, `astro build`, or any other step that could fail along with the main site's build. +- **Manual trigger only.** Same pattern as `deploy.yml` — `workflow_dispatch` from the Gitea UI. No push/auto-deploy. +- **Cannot race the main deploy.** Both workflows write to the same docroot via `rsync --delete`; concurrent runs would clobber each other. +- **Recovery is the regular deploy.** Triggering `deploy.yml` on any `main` commit restores the site. No bespoke "un-placeholder" workflow. +- **Origin-side headers are not in scope.** Hetzner Webhosting L runs `AllowOverride None`; all response headers are owned by Cloudflare Transform Rules (see `OPERATOR-CHECKLIST.md` §2). The placeholder workflow does NOT need to assert HSTS/CSP/XFO — those headers are origin-agnostic. + +Out of scope: +- Per-environment placeholder variants (staging, etc.). Same target, same content. +- A status page, ETA, or live incident feed. +- Cookie banner, analytics, or any third-party JS. + +## 3. Architecture + +``` +cameleer-website/ +├── placeholder/ +│ ├── index.html # standalone HTML, inlined CSS +│ ├── cameleer-logo.png # copy of public/icons/cameleer-192.png (~36 KB) +│ └── favicon.png # copy of public/icons/cameleer-32.png (~2.4 KB) +├── .gitea/workflows/ +│ ├── ci.yml # unchanged +│ ├── deploy.yml # unchanged +│ └── deploy-placeholder.yml # NEW +└── README.md # NEW section: "Placeholder mode" +``` + +### Why standalone HTML, not Astro + +The placeholder lives outside `src/`, is not picked up by `astro build`, and never enters `dist/`. It is a single self-contained `index.html` with inlined CSS. Two PNG assets ship alongside the HTML in `placeholder/` and are referenced by relative paths (`./cameleer-logo.png`, `./favicon.png`) so the page renders correctly after `rsync --delete` clears the docroot. The repo's full-resolution `public/cameleer-logo.svg` is 1.5 MB (embedded raster data) and is therefore not used here; the 192 px PNG is the right size and weight for a placeholder hero. + +Trade-off accepted: brand tokens (colors, fonts) are hand-mirrored from `tailwind.config.mjs` rather than imported. If those tokens change, the placeholder may visibly drift. Acceptable because (a) the placeholder is rarely shown, (b) the file is short enough to re-sync in two minutes, (c) the alternative — coupling the placeholder to the Astro build — defeats the placeholder's whole reason for existing. + +### Workflow shape + +`deploy-placeholder.yml`: + +- **Trigger:** `workflow_dispatch` only. +- **Concurrency:** `group: deploy-production`, `cancel-in-progress: false` — same group as `deploy.yml`. Gitea will queue, never overlap. +- **Runner:** `ubuntu-latest` (matches `deploy.yml`). +- **Secrets used:** `SFTP_HOST`, `SFTP_USER`, `SFTP_PATH`, `SFTP_KEY`, `SFTP_KNOWN_HOSTS`, `PUBLIC_SALES_EMAIL`. +- **Steps:** + 1. `actions/checkout@v4` + 2. Configure SSH (key + known_hosts; install rsync/openssh if missing) — same logic as `deploy.yml` lines 70–88. + 3. **Inject `PUBLIC_SALES_EMAIL` into the placeholder.** The HTML contains a single literal token `__SALES_EMAIL__` (no hyphens, no other instances anywhere); `sed -i "s|__SALES_EMAIL__|$PUBLIC_SALES_EMAIL|g" placeholder/index.html`. Fail loudly with `: "${PUBLIC_SALES_EMAIL:?...}"` first. Verify replacement by grepping that the token no longer appears. + 4. `rsync -avz --delete --rsync-path=/usr/bin/rsync` over `ssh -p 222` of `placeholder/` → `$SFTP_PATH/` — same flags and SSH options as `deploy.yml` lines 107–109. + 5. **Smoke test:** `curl -s https://www.cameleer.io/ | grep -q 'Routes are remapping'` — placeholder-unique sentinel. Fail the workflow if absent. (Skip the security-headers grep from `deploy.yml`; those headers come from Cloudflare and apply equally to placeholder responses, so they're already covered.) + +### Why `rsync --delete` + +Matches `deploy.yml` behaviour. The docroot reflects exactly what the placeholder ships, with no leftover assets from a previous real-site deploy lingering and being indexed. + +## 4. Placeholder content + +### Markup outline + +Single `` document. Sections, in order: + +1. ``: + - `Cameleer — Back shortly` + - `` + - `` + - `` and `` (matches `BaseLayout.astro`) + - `` — the 32 px PNG that ships in `placeholder/`. Relative path so it resolves against the docroot root after rsync. + - Google Fonts `` for DM Sans (400, 700) and JetBrains Mono (400). Single preconnect. + - Inlined ` + + +
+ +

✦ Routes are remapping.

+

We're back on the trail
in a moment.

+

Cameleer is the hosted runtime and observability platform for Apache Camel — auto-traced, replay-ready, cross-service correlated. The 3 AM page becomes a 30-second answer.

+

cameleer.io · __SALES_EMAIL__

+
+ + +``` + +- [ ] **Step 3: Run the placeholder tests to verify they pass** + +Run: `npm test -- placeholder` + +Expected: all tests in `src/placeholder.test.ts` pass. (`-- placeholder` is a vitest test-name filter that runs only the new file's `describe` blocks for fast feedback.) + +- [ ] **Step 4: Run the full test suite to verify no regressions** + +Run: `npm test` + +Expected: every existing test still passes (the middleware/CSP suite is the only other one). + +- [ ] **Step 5: Visual verification in a browser** + +The `__SALES_EMAIL__` token will be visible in the rendered page — that is expected; it's substituted at deploy time. Confirm visual treatment. + +```bash +npx serve placeholder -l 4322 +# then open http://localhost:4322 in a browser +``` + +Confirm by eye: +1. Centered single-column layout, logo on top. +2. Dark background (#060a13) with a faint amber radial glow centered. +3. Italic amber eyebrow `✦ Routes are remapping.`. +4. Bold display heading wraps onto two lines on desktop ("We're back on the trail" / "in a moment."). +5. Subhead reads as muted body text below. +6. Mono microcopy at the bottom shows `cameleer.io · __SALES_EMAIL__` in faint grey, with the token rendered as a `mailto:` link. +7. Resize the window to ~360 px wide — layout stays centered, heading scales down via `clamp()`, no horizontal scroll. + +Stop the server with Ctrl-C when done. + +- [ ] **Step 6: Commit the placeholder page and assets** + +```bash +git add placeholder/index.html placeholder/cameleer-logo.png placeholder/favicon.png +git commit -m "feat(placeholder): add under-construction page with branded teaser + +Standalone HTML + two sibling PNGs, no Astro build dependency. +Carries __SALES_EMAIL__ substitution tokens that the new deploy +workflow replaces at deploy time." +``` + +--- + +## Task 3: Add the deploy-placeholder workflow + +**Files:** +- Create: `.gitea/workflows/deploy-placeholder.yml` + +- [ ] **Step 1: Write `.gitea/workflows/deploy-placeholder.yml`** + +Create with this exact content. Mirrors `deploy.yml`'s SSH/rsync pattern but skips `npm ci`/`astro build`/Lighthouse — the placeholder is hand-authored static and must be deployable when the main build is broken. + +```yaml +# ----------------------------------------------------------------------------- +# cameleer-website — Deploy under-construction placeholder +# +# MANUAL TRIGGER ONLY. Replaces the live cameleer.io docroot with a static +# "back shortly" page. Recovery: trigger Actions → deploy → Run workflow on +# the desired main commit. +# +# Shares the deploy-production concurrency group with deploy.yml so the two +# workflows queue rather than race on the same docroot. +# +# This workflow does NOT run npm/astro. The placeholder is hand-authored +# static HTML in placeholder/, deliberately decoupled from the main build so +# it can ship even when the main build is broken (which is the worst case in +# which a placeholder is needed). +# +# Required secrets (repo settings → Actions → Secrets): +# SFTP_HOST, SFTP_USER, SFTP_PATH, SFTP_KEY, SFTP_KNOWN_HOSTS +# PUBLIC_SALES_EMAIL +# ----------------------------------------------------------------------------- + +name: deploy-placeholder + +on: + workflow_dispatch: + +concurrency: + group: deploy-production + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v4 + + - name: Substitute sales email into placeholder + env: + PUBLIC_SALES_EMAIL: ${{ secrets.PUBLIC_SALES_EMAIL }} + run: | + set -e + : "${PUBLIC_SALES_EMAIL:?PUBLIC_SALES_EMAIL secret must be set}" + sed -i "s|__SALES_EMAIL__|${PUBLIC_SALES_EMAIL}|g" placeholder/index.html + if grep -q '__SALES_EMAIL__' placeholder/index.html; then + echo "Token __SALES_EMAIL__ still present after substitution — refusing to ship." + exit 1 + fi + + - name: Configure SSH + env: + SFTP_KEY: ${{ secrets.SFTP_KEY }} + SFTP_KNOWN_HOSTS: ${{ secrets.SFTP_KNOWN_HOSTS }} + run: | + set -e + : "${SFTP_KEY:?SFTP_KEY secret must be set}" + : "${SFTP_KNOWN_HOSTS:?SFTP_KNOWN_HOSTS secret must be set}" + mkdir -p ~/.ssh + printf '%s\n' "$SFTP_KEY" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + printf '%s\n' "$SFTP_KNOWN_HOSTS" > ~/.ssh/known_hosts + chmod 644 ~/.ssh/known_hosts + if ! command -v rsync >/dev/null 2>&1 || ! command -v ssh >/dev/null 2>&1; then + if command -v sudo >/dev/null 2>&1; then SUDO=sudo; else SUDO=; fi + $SUDO apt-get update -qq + $SUDO apt-get install -y --no-install-recommends rsync openssh-client + fi + + - name: Deploy via rsync + env: + SFTP_USER: ${{ secrets.SFTP_USER }} + SFTP_HOST: ${{ secrets.SFTP_HOST }} + SFTP_PATH: ${{ secrets.SFTP_PATH }} + run: | + : "${SFTP_USER:?SFTP_USER secret must be set}" + : "${SFTP_HOST:?SFTP_HOST secret must be set}" + : "${SFTP_PATH:?SFTP_PATH secret must be set}" + # Hetzner Webhosting splits SSH into two ports: + # port 22 — SFTP only, no remote command exec + # port 222 — full SSH with shell exec (rsync needs this) + # `--rsync-path=/usr/bin/rsync` tells the local rsync where to find + # the remote binary on Hetzner's locked-down PATH. + # `BatchMode=yes` disables interactive prompts. + rsync -avz --delete --rsync-path=/usr/bin/rsync \ + -e "ssh -p 222 -i $HOME/.ssh/id_ed25519 -o BatchMode=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$HOME/.ssh/known_hosts" \ + placeholder/ "$SFTP_USER@$SFTP_HOST:$SFTP_PATH/" + + - name: Post-deploy smoke test + run: | + set -e + echo "Confirming the placeholder is live on www.cameleer.io..." + BODY=$(curl -sf https://www.cameleer.io/) + echo "$BODY" | grep -qF 'Routes are remapping' \ + || { echo "Sentinel string missing — placeholder did not land."; exit 1; } + echo "$BODY" | grep -qF 'mailto:' \ + || { echo "mailto: link missing — sales email substitution may have failed."; exit 1; } + curl -sfI https://www.cameleer.io/cameleer-logo.png > /dev/null \ + || { echo "cameleer-logo.png not reachable on the live origin."; exit 1; } + echo "Placeholder is live." +``` + +- [ ] **Step 2: Verify the YAML parses** + +Run a quick Node-based parse check (no extra dep needed; Node ships with no YAML parser, so use a one-off `npx`): + +```bash +npx --yes js-yaml .gitea/workflows/deploy-placeholder.yml > /dev/null && echo "YAML OK" +``` + +Expected: `YAML OK`. If `js-yaml` errors, re-read the file for stray tabs or unbalanced quoting. + +- [ ] **Step 3: Verify the existing deploy.yml is unchanged** + +Run: `git diff .gitea/workflows/deploy.yml` + +Expected: empty output (the new workflow is additive only). + +- [ ] **Step 4: Commit the workflow** + +```bash +git add .gitea/workflows/deploy-placeholder.yml +git commit -m "ci(deploy): add deploy-placeholder workflow + +Manual-trigger workflow that substitutes PUBLIC_SALES_EMAIL into +placeholder/index.html, rsyncs placeholder/ to the Hetzner docroot +over SSH:222, then smoke-tests the live origin for the sentinel +string, mailto link, and logo URL. + +Shares the deploy-production concurrency group with deploy.yml so +the two workflows can never race on the same docroot. Recovery is +the regular deploy.yml — no separate un-placeholder workflow." +``` + +--- + +## Task 4: Document the placeholder mode in the README + +**Files:** +- Modify: `README.md` (append a subsection under "## Deployment", after the existing "Rollback" paragraph and before "**Security headers**") + +- [ ] **Step 1: Read the current README to locate the insertion point** + +Run: `cat README.md` + +Locate the line `Rollback: trigger the deploy workflow on the previous \`main\` commit (Actions UI lets you pick a ref).`. The new subsection goes immediately after it, separated by a blank line, before the `**Security headers**` paragraph. + +- [ ] **Step 2: Insert the placeholder-mode subsection** + +Use the `Edit` tool with these exact arguments. `old_string` is the existing two-paragraph boundary; `new_string` reproduces it with the new `### Placeholder mode` subsection wedged in between. + +`file_path`: `README.md` + +`old_string`: +```` +Rollback: trigger the deploy workflow on the previous `main` commit (Actions UI lets you pick a ref). + +**Security headers** (HSTS, CSP, X-Frame-Options, etc.) are owned by **Cloudflare Transform Rules**, not by anything in this repo. +```` + +`new_string`: +```` +Rollback: trigger the deploy workflow on the previous `main` commit (Actions UI lets you pick a ref). + +### Placeholder mode + +To put the site into "back shortly" mode, trigger Gitea → **Actions → deploy-placeholder → Run workflow**. To bring the real site back, trigger **Actions → deploy → Run workflow** on the desired `main` commit. Both workflows share the `deploy-production` concurrency group, so they can never run simultaneously. + +The placeholder is hand-authored static HTML in `placeholder/` and does NOT depend on `npm`/`astro build` — it is deliberately decoupled from the main build so it can ship even when that build is broken. + +**Security headers** (HSTS, CSP, X-Frame-Options, etc.) are owned by **Cloudflare Transform Rules**, not by anything in this repo. +```` + +- [ ] **Step 3: Verify the README still renders cleanly** + +Run: `cat README.md | head -60` + +Confirm by eye that the new subsection appears under "Deployment", the surrounding paragraphs are intact, and there is exactly one blank line between adjacent blocks. + +- [ ] **Step 4: Commit the README update** + +```bash +git add README.md +git commit -m "docs(readme): add placeholder mode section + +Documents the deploy-placeholder workflow trigger and the recovery +path back to the real site via deploy.yml." +``` + +--- + +## Final verification + +- [ ] **Run the full test suite one more time** + +Run: `npm test` + +Expected: all tests pass — both `src/middleware.test.ts` and `src/placeholder.test.ts`. + +- [ ] **Confirm the four commits are in place** + +Run: `git log --oneline -5` + +Expected (top-down): README docs commit, deploy-placeholder.yml commit, placeholder feat commit, placeholder test commit, then the spec commit (`9b0c36b docs(spec): ...`). + +- [ ] **Sanity-check the placeholder directory ships only what it should** + +Run: `ls -la placeholder/` + +Expected: exactly three files — `index.html`, `cameleer-logo.png`, `favicon.png`. No stray `.test.ts`, `.DS_Store`, etc. (If anything else appears, remove it before merging — `rsync --delete` would otherwise push it to the live origin.) + +- [ ] **Push and trigger the first real run** (operator step, not part of the implementation) + +Push the branch, merge to `main` once reviewed, then in Gitea: **Actions → deploy-placeholder → Run workflow** on `main`. Verify by visiting `https://www.cameleer.io/` that the placeholder renders, then trigger **Actions → deploy → Run workflow** to restore the real site. From 49fdd96f4f8423bd6337e5566a164720fa7c48b7 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sat, 25 Apr 2026 18:05:52 +0200 Subject: [PATCH 4/9] test(placeholder): add static-content tests for under-construction page --- src/placeholder.test.ts | 72 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/placeholder.test.ts diff --git a/src/placeholder.test.ts b/src/placeholder.test.ts new file mode 100644 index 0000000..2fdcbdc --- /dev/null +++ b/src/placeholder.test.ts @@ -0,0 +1,72 @@ +import { describe, it, expect } from 'vitest'; +import { existsSync, readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +const placeholderDir = join(process.cwd(), 'placeholder'); +const indexPath = join(placeholderDir, 'index.html'); + +describe('placeholder/index.html', () => { + const html = readFileSync(indexPath, 'utf8'); + + it('starts with the HTML5 doctype', () => { + expect(html.toLowerCase().trimStart()).toMatch(/^/); + }); + + it('has the back-shortly title', () => { + expect(html).toContain('Cameleer — Back shortly'); + }); + + it('is not indexable by search engines', () => { + expect(html).toContain(''); + }); + + it('declares the dark color-scheme matching the live site', () => { + expect(html).toContain(''); + expect(html).toContain(''); + }); + + it('contains the sentinel string the deploy workflow greps for', () => { + // The workflow's post-deploy smoke test fails if this string is missing. + expect(html).toContain('Routes are remapping'); + }); + + it('uses the live hero subhead verbatim', () => { + expect(html).toContain( + 'Cameleer is the hosted runtime and observability platform for Apache Camel — auto-traced, replay-ready, cross-service correlated. The 3 AM page becomes a 30-second answer.' + ); + }); + + it('contains __SALES_EMAIL__ tokens at both the mailto href and the link text', () => { + const matches = html.match(/__SALES_EMAIL__/g) ?? []; + expect(matches.length).toBeGreaterThanOrEqual(2); + }); + + it('contains no other __TOKEN__ style placeholders', () => { + // Guard against a forgotten token that would survive the sed substitution. + const allTokens = html.match(/__[A-Z][A-Z0-9_]+__/g) ?? []; + const nonSales = allTokens.filter((t) => t !== '__SALES_EMAIL__'); + expect(nonSales).toEqual([]); + }); + + it('references the sibling cameleer-logo.png by relative path', () => { + expect(html).toContain('src="./cameleer-logo.png"'); + }); + + it('references the sibling favicon.png by relative path', () => { + expect(html).toContain('href="./favicon.png"'); + }); + + it('has no