`, so the selector
+ `.ui.error.message` matches it.
+* `.ui.errorPopup.message` — dismissable popup error (see
+ `src/components/error_display.tsx`). As above, `ui` and `message` come from
+ Semantic UI React's `
`, and `errorPopup` comes from the
+ custom `className="errorPopup"` prop. Note: `ErrorPopup` uses Semantic
+ UI React's ``, which renders its content at `document.body`
+ level, not inside `#content` in the DOM. When the popup is closed
+ (`open={false}`), the Portal renders nothing, so this assertion
+ verifies absence rather than visibility.
+
+The side panel is expanded by default on desktop viewports (the prober project
+uses `devices['Desktop Chrome']`). The `getShowSidePanel` function in
+`src/util/url_args.ts:177` returns `true` on non-mobile screens, so the `.details`
+container is visible without any URL parameters.
+
+### Step 3: Prober GitHub Actions workflows
+
+All prober workflows should declare minimal permissions for security:
+
+```yaml
+permissions:
+ contents: read
+ actions: write
+```
+
+All prober workflows should use `actions/checkout@v4` (not v2, which is
+used by some older deploy workflows).
+
+All prober workflows should set `timeout-minutes: 15` on each job to prevent
+hanging runs from consuming runner minutes (default GitHub Actions timeout is
+6 hours).
+
+All prober workflows should define a `concurrency` group to prevent
+overlapping runs (e.g., a deploy-triggered run overlapping with a
+schedule-triggered run):
+
+```yaml
+concurrency:
+ group: prober-${{ github.workflow }}
+ cancel-in-progress: false
+```
+
+`cancel-in-progress: false` ensures a deploy-triggered run is not cancelled
+by a scheduled run — both complete independently.
+
+Four reusable workflow files, one per prober. The three live-URL probers
+are identical in structure — only the name and artifact name differ. The
+Docker prober has a different structure (it builds and runs the container
+before testing).
+
+**Create:** `.github/workflows/prober-wikitree.yml`
+
+* **Triggers:** `workflow_call` (with `wait_for_propagation` input),
+ `workflow_dispatch` (with `wait_for_propagation` input), `schedule`
+ (daily at `0 5 * * *` UTC = ~6:00/7:00 CET).
+* **Depends on (when called from deploy):** `deploy-wikitree-apps.yml`
+ only.
+* **Artifact name:** `prober-report-wikitree`.
+
+**Create:** `.github/workflows/prober-gh-pages.yml`
+
+* Same structure.
+* **Depends on (when called from deploy):** `deploy-gh-pages.yml` only.
+* **Artifact name:** `prober-report-gh-pages`.
+
+**Create:** `.github/workflows/prober-wikitree-cors.yml`
+
+* Same structure.
+* **Depends on (when called from deploy):** `deploy-wikitree-apps.yml`
+ only.
+* **Artifact name:** `prober-report-wikitree-cors`.
+
+**Create:** `.github/workflows/prober-docker.yml`
+
+* **Triggers:** `workflow_call`, `workflow_dispatch`, `schedule`
+ (daily at `0 5 * * *` UTC).
+* **Depends on (when called from deploy):** `deploy-docker.yml` only.
+* **Artifact name:** `prober-report-docker`.
+* **No `wait_for_propagation` input** — The Docker container is available
+ immediately after startup; no propagation delay is needed.
+
+**Shared workflow structure** (live-URL probers):
+
+```
+1. Checkout repository (actions/checkout@v4).
+2. Setup Node.js 24.x with npm cache.
+3. Run npm ci.
+4. If wait_for_propagation is true, sleep 180 seconds.
+5. Get Playwright version (same pattern as node.js.yml: extract version
+ from @playwright/test/package.json into a cache key).
+6. Cache Chromium browser binaries (keyed by Playwright version). If cache
+ misses, install Playwright with system dependencies
+ (npx playwright install-deps chromium && npx playwright install
+ chromium). With daily + post-deploy runs, caching avoids re-downloading
+ ~150MB on every run.
+7. Run: npx playwright test --config=playwright.prober.config.ts "${SPEC}"
+ Each workflow sets a SPEC environment variable (e.g.,
+ SPEC=wikitree.spec.ts) to select only the relevant spec file. Without
+ this filter, Playwright would run all specs in the testDir for every
+ prober workflow.
+8. Upload Playwright HTML report as artifact (if: always()). Set
+ PLAYWRIGHT_HTML_REPORT=playwright-report/prober to avoid path
+ conflicts with other report artifacts. Set `retention-days: 30` to
+ limit storage consumption — prober runs (daily + post-deploy) generate
+ traces, screenshots, and videos that can accumulate quickly.
+```
+
+**Docker prober workflow structure** (different from live-URL probers):
+
+```
+1. Checkout repository (actions/checkout@v4).
+2. Pull Docker image: docker pull ghcr.io/pewu/topola-viewer:latest
+ (Pull the image published by deploy-docker.yml. This tests the actual
+ published artifact, not a local build. The GHCR package is public, so
+ no `docker login` authentication step is required.)
+3. Run container: docker run -d -p 8080:8080 -e STATIC_URL=test.ged
+ -v $(pwd)/src/datasource/testdata/test.ged:/app/public/test.ged
+ --name topola-prober-${{ github.run_id }}
+ ghcr.io/pewu/topola-viewer:latest
+ (Use a unique container name with github.run_id to prevent name
+ conflicts if a previous run didn't clean up or if runs overlap.)
+4. Wait for container to be ready: use a bash retry loop with `curl` to
+ poll `http://localhost:8080/` until it responds with HTTP 200
+ (timeout 30s, 1s interval):
+ ```bash
+ for i in $(seq 1 30); do
+ if curl -sf -o /dev/null http://localhost:8080/; then break; fi
+ sleep 1
+ done
+ curl -sf -o /dev/null http://localhost:8080/
+ ```
+ The final `curl` ensures the workflow fails with a clear error if
+ the container never became ready. This prevents a race condition
+ where the test runs before Caddy is ready to serve requests.
+5. Setup Node.js 24.x with npm cache.
+6. Run npm ci.
+7. Get Playwright version (same pattern as node.js.yml).
+8. Cache and install Playwright (same as live-URL probers).
+9. Run: npx playwright test --config=playwright.prober.config.ts docker.spec.ts
+10. Upload Playwright HTML report as artifact (if: always()). Set
+ `retention-days: 30` (same as live-URL probers).
+11. Stop and remove container (if: always()): docker stop
+ topola-prober-${{ github.run_id }} 2>/dev/null; docker rm
+ topola-prober-${{ github.run_id }} 2>/dev/null; true
+ (The if: always() ensures cleanup runs even on failure. The
+ 2>/dev/null and trailing true prevent errors if the container was
+ never started, e.g., pull failed at step 2.)
+```
+
+**`wait_for_propagation` input flag** (live-URL probers only):
+
+* Defined under both `workflow_call` and `workflow_dispatch` triggers.
+* Type: `boolean`, default: `false`.
+* When invoked from `deploy-everywhere.yml`, passed as `true`.
+* When triggered by schedule or manual (unchecked), defaults to `false`.
+* The sleep step uses `if: inputs.wait_for_propagation` to conditionally
+ execute.
+* The Docker prober workflow does not define this input — the container is
+ available immediately after `docker run`, so no propagation wait is needed.
+
+### Step 4: Modify deploy-everywhere workflow
+
+**Modify:** `.github/workflows/deploy-everywhere.yml`
+
+Add four prober jobs that call the reusable prober workflows. Each prober
+depends only on its relevant deploy job, not on all deploys.
+
+Current state (before changes):
+
+```yaml
+jobs:
+ deploy-gh-pages:
+ uses: ./.github/workflows/deploy-gh-pages.yml
+ secrets: inherit
+ deploy-wikitree-apps:
+ uses: ./.github/workflows/deploy-wikitree-apps.yml
+ secrets: inherit
+ deploy-docker:
+ uses: ./.github/workflows/deploy-docker.yml
+ secrets: inherit
+```
+
+After changes:
+
+```yaml
+jobs:
+ deploy-gh-pages:
+ uses: ./.github/workflows/deploy-gh-pages.yml
+ secrets: inherit
+ deploy-wikitree-apps:
+ uses: ./.github/workflows/deploy-wikitree-apps.yml
+ secrets: inherit
+ deploy-docker:
+ uses: ./.github/workflows/deploy-docker.yml
+ secrets: inherit
+
+ prober-wikitree:
+ needs: deploy-wikitree-apps
+ uses: ./.github/workflows/prober-wikitree.yml
+ secrets: inherit
+ with:
+ wait_for_propagation: true
+ prober-gh-pages:
+ needs: deploy-gh-pages
+ uses: ./.github/workflows/prober-gh-pages.yml
+ secrets: inherit
+ with:
+ wait_for_propagation: true
+ prober-wikitree-cors:
+ needs: deploy-wikitree-apps
+ uses: ./.github/workflows/prober-wikitree-cors.yml
+ secrets: inherit
+ with:
+ wait_for_propagation: true
+ prober-docker:
+ needs: deploy-docker
+ uses: ./.github/workflows/prober-docker.yml
+ secrets: inherit
+```
+
+Rationale for dependency mapping:
+
+* `prober-wikitree` needs `deploy-wikitree-apps` — it tests the WikiTree
+ deployment.
+* `prober-gh-pages` needs `deploy-gh-pages` — it tests the GitHub Pages
+ deployment.
+* `prober-wikitree-cors` needs `deploy-wikitree-apps` — it tests the
+ WikiTree deployment (with CORS proxy).
+* `prober-docker` needs `deploy-docker` — it tests the Docker image
+ published to GHCR by `deploy-docker.yml` (Dockerfile, Caddy config, app
+ startup). It does not pass `wait_for_propagation` because the container is
+ available immediately after `docker run`.
+* If a prober fails, the `deploy-everywhere` workflow is marked as failed
+ (red X), triggering an email notification (if GitHub email notifications
+ are enabled — see note in Section 2).
+* Note: Individual deploy workflows (`deploy-gh-pages.yml`,
+ `deploy-wikitree-apps.yml`, `deploy-docker.yml`) also support
+ `workflow_dispatch`. If a deploy is triggered directly (instead of
+ through `deploy-everywhere.yml`), no probers run because probers are only
+ called from `deploy-everywhere.yml`. To ensure probers always run after a
+ deploy, always trigger deploys through `deploy-everywhere.yml`.
+
+### Step 5: Update supporting files
+
+**Modify:** `playwright.config.ts`
+
+Add `testIgnore: ['*_visual.spec.ts', 'probers/**']` to the e2e project to
+prevent prober specs in `tests/probers/` from being discovered by the regular
+CI e2e test run. Without this, `npm run test:e2e` would try to execute
+prober specs against the local dev server, causing failures.
+
+**Modify:** `package.json`
+
+Add a `test:probers` script for running probers locally during development:
+
+```json
+"test:probers": "playwright test --config=playwright.prober.config.ts"
+```
+
+**Modify:** `tests/tsconfig.json`
+
+Add `probers/` to the `include` array so prober specs are type-checked by
+`tsc -p tests/tsconfig.json --noEmit` (which runs in CI via
+`node.js.yml`).
+
+Current state:
+
+```json
+{
+ "compilerOptions": { ... },
+ "include": ["./**/*.ts", "./**/*.d.ts"]
+}
+```
+
+The existing `./**/*.ts` glob already includes `tests/probers/` — no
+modification needed. The `./**/*.d.ts` glob covers type declaration files
+and does not affect prober spec discovery.
+
+**Modify:** `.github/workflows/README.md`
+
+Add entries for the four new prober workflows to the file registry, e.g.:
+
+```markdown
+- [prober-wikitree.yml](prober-wikitree.yml): Reusable prober that
+ smoke-tests the WikiTree direct API path on the live WikiTree deployment.
+ Runs daily and after deploy.
+- [prober-gh-pages.yml](prober-gh-pages.yml): Reusable prober that
+ smoke-tests the GitHub Pages deployment with GEDCOM-from-URL through the
+ CORS proxy. Runs daily and after deploy.
+- [prober-wikitree-cors.yml](prober-wikitree-cors.yml): Reusable prober
+ that smoke-tests the CORS proxy from the WikiTree deployment with
+ GEDCOM-from-URL. Runs daily and after deploy.
+- [prober-docker.yml](prober-docker.yml): Reusable prober that
+ smoke-tests the published Docker image from GHCR (Dockerfile, Caddy
+ config, app startup) by pulling and running it locally. Runs daily and
+ after deploy.
+```
+
+**Create:** `tests/probers/README.md`
+
+Document the prober test directory, explaining that these are live smoke
+tests (not hermetic), how to run them locally (`npm run test:probers`), and
+that they require network access to external services (WikiTree API, CORS
+proxy, GitHub raw URLs).
+
+**Modify:** `PROJECT_STRUCTURE.md`
+
+Add entries for the new `tests/probers/` directory and
+`playwright.prober.config.ts` file.
+
+**Modify:** `docs/README.md`
+
+Add an entry for this design document to the registry:
+
+```markdown
+* **[PROBERS_DESIGN.md](PROBERS_DESIGN.md)**: Live prober smoke tests
+ against deployed GitHub Pages, WikiTree URLs, and local Docker container,
+ covering WikiTree API, CORS proxy, GEDCOM-from-URL, and Docker build paths.
+```
+
+### Summary of all files
+
+| File | Action | Purpose |
+|---|---|---|
+| `playwright.prober.config.ts` | Create | Separate Playwright config for probers (no local server, live URLs) |
+| `playwright.config.ts` | Modify | Add `testIgnore` for `probers/**` to e2e project |
+| `package.json` | Modify | Add `test:probers` script |
+| `tests/probers/wikitree.spec.ts` | Create | WikiTree direct API smoke test |
+| `tests/probers/gh-pages-gedcom.spec.ts` | Create | GitHub Pages + CORS proxy smoke test |
+| `tests/probers/wikitree-cors-gedcom.spec.ts` | Create | WikiTree + CORS proxy smoke test |
+| `tests/probers/docker.spec.ts` | Create | Docker container smoke test |
+| `.github/workflows/prober-wikitree.yml` | Create | Reusable workflow: WikiTree prober |
+| `.github/workflows/prober-gh-pages.yml` | Create | Reusable workflow: GH Pages prober |
+| `.github/workflows/prober-wikitree-cors.yml` | Create | Reusable workflow: WikiTree CORS prober |
+| `.github/workflows/prober-docker.yml` | Create | Reusable workflow: Docker prober (pulls GHCR image) |
+| `.github/workflows/deploy-everywhere.yml` | Modify | Add prober jobs with targeted deploy dependencies |
+| `tests/tsconfig.json` | Modify | Ensure prober specs are type-checked |
+| `tests/probers/README.md` | Create | Document prober directory and usage |
+| `.github/workflows/README.md` | Modify | Document new prober workflows |
+| `docs/README.md` | Modify | Add prober design doc to registry |
+| `PROJECT_STRUCTURE.md` | Modify | Add prober directory and config file |
+
+
+## 5. Future Considerations
+
+### WikiTree Login Flow Prober
+
+The current WikiTree prober tests the unauthenticated API path (loading a
+public profile without an authcode). A future prober could test the
+authenticated login flow — logging in with an authcode and verifying that
+private profiles are accessible. This would require obtaining a dedicated
+test account on wikitree.com and storing the authcode as a GitHub Actions
+secret. This is deferred because it adds complexity (secret management,
+authcode expiry, test account maintenance) and the unauthenticated path
+already covers the most common deployment scenario.
+
+### Google Drive Integration Prober
+
+A prober for the Google Drive integration (loading a GEDCOM file from
+Google Drive) is not included. Google's OAuth flow is designed for human
+interaction and includes bot detection (CAPTCHA, device verification) that
+would likely prevent automated login. Additionally, the Google Drive
+integration requires `VITE_GOOGLE_CLIENT_ID` and `VITE_GOOGLE_API_KEY`
+secrets, which are not available in the prober environment. A possible
+workaround would be to use a pre-authorized service account or a long-lived
+refresh token stored as a secret, but this is complex and fragile. This is
+deferred until a reliable automation approach is identified.