From c3511a4c1bc2a4ad3b4277c5c30b6570b72c17af Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sat, 25 Apr 2026 18:03:27 +0200 Subject: [PATCH] docs(plan): implementation plan for under-construction placeholder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four-task plan: tests → page+assets → workflow → README. TDD via src/placeholder.test.ts (vitest only discovers src/**/*.test.ts). Atomic commits per task; the plan is wired off the spec at commit 9b0c36b. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...26-04-25-under-construction-placeholder.md | 518 ++++++++++++++++++ 1 file changed, 518 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-25-under-construction-placeholder.md diff --git a/docs/superpowers/plans/2026-04-25-under-construction-placeholder.md b/docs/superpowers/plans/2026-04-25-under-construction-placeholder.md new file mode 100644 index 0000000..67ce520 --- /dev/null +++ b/docs/superpowers/plans/2026-04-25-under-construction-placeholder.md @@ -0,0 +1,518 @@ +# Under-construction placeholder 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:** Ship a branded "back shortly" page for cameleer.io plus a manual-trigger Gitea workflow that swaps it onto the live origin on demand, recoverable by re-running the existing `deploy.yml`. + +**Architecture:** Standalone HTML in a top-level `placeholder/` directory, plus two PNG asset copies. A new `.gitea/workflows/deploy-placeholder.yml` rsyncs that directory to the same Hetzner docroot used by `deploy.yml` (`--delete` enabled, `deploy-production` concurrency group shared so the two workflows queue rather than race). No Astro build dependency, so the placeholder still ships when the main build is broken — which is the worst case where one is needed. + +**Tech Stack:** Plain HTML5 + inlined CSS, Google Fonts (DM Sans, JetBrains Mono), bash + rsync over SSH:222, Vitest 1 for static-content assertions. + +**Spec:** `docs/superpowers/specs/2026-04-25-under-construction-placeholder-design.md` (commit `9b0c36b`). + +--- + +## File Structure + +| File | Status | Responsibility | +|---|---|---| +| `placeholder/index.html` | Create | Static under-construction page. Single self-contained file, references the two sibling PNGs by relative path. Contains `__SALES_EMAIL__` substitution token (used twice — `mailto:` href and link text). | +| `placeholder/cameleer-logo.png` | Create (copy) | Hero logo. Copy of `public/icons/cameleer-192.png` (~36 KB). | +| `placeholder/favicon.png` | Create (copy) | Browser tab icon. Copy of `public/icons/cameleer-32.png` (~2.4 KB). | +| `src/placeholder.test.ts` | Create | Static assertions that the placeholder HTML has the contract the deploy workflow depends on (sentinel string, token, references, no JS, etc.). Lives in `src/` because `vitest.config.ts` only discovers `src/**/*.test.ts`. | +| `.gitea/workflows/deploy-placeholder.yml` | Create | Manual-dispatch workflow: substitute sales email → rsync `placeholder/` to docroot → smoke-test the live origin. | +| `README.md` | Modify | Append a "Placeholder mode" subsection under "Deployment". | + +--- + +## Task 1: Add tests for the placeholder HTML + +**Why first:** Establishes the contract the workflow depends on — sentinel string, substitution token, asset references — before any markup is written, so we can't accidentally drop one of them later. + +**Files:** +- Create: `src/placeholder.test.ts` + +- [ ] **Step 1: Write the failing test file** + +Create `src/placeholder.test.ts` with this exact content: + +```typescript +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