# 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