Skip to content

Move public demo to a standalone demo.diffsentry.app container#73

Merged
mk7luke merged 9 commits into
mainfrom
move-demo
Jul 4, 2026
Merged

Move public demo to a standalone demo.diffsentry.app container#73
mk7luke merged 9 commits into
mainfrom
move-demo

Conversation

@mk7luke

@mk7luke mk7luke commented Jul 4, 2026

Copy link
Copy Markdown
Owner

What & why

The public demo was served from my real instance. The changes in this PR make it a standalone artifact so my instance carries no anonymous public surface.

The demo is the dashboard SPA's existing fixture-backed demo mode (zero network calls). A new build-time flag serves it at the domain root.

Changes

  • VITE_FORCE_DEMO build flag (web/src/demo/mode.ts, vite.config.ts, vite-env.d.ts): hard-locks demo mode, router basename /, detection kept on the literal /demo (not the basename). PWA/service-worker disabled in the forced build via VitePWA({ disable }).
  • Redirect-loop guard (web/src/main.tsx): the /demo bounce is skipped under FORCE_DEMO.
  • build:web:demo script; Dockerfile.demo + deploy/demo-nginx.conf → nginx image serving the fixture SPA on port 3027, SPA fallback, /health, security headers, CSP pinned to the app's one inline theme-bootstrap script (sha256). Ships no server, DB, keys, or .diffsentry.yaml.
  • demo service in docker-compose.yml (3027:80); .dockerignore for reproducible builds.

Verification (Playwright on :3027)

  • Serves at root (//overview, no /demo prefix, no redirect loop).
  • Zero /api requests, zero console/CSP errors; deep links render fixtures with the demo-mode banner.

Summary

Added a standalone nginx configuration to serve the public demo as a static SPA on its own host. Hardened the demo container with SPA routing, caching rules, security headers, and a pinned CSP.

Changes

File Changes
deploy/demo-nginx.conf Introduced an nginx server config for demo.diffsentry.app that serves the built SPA from static files, adds SPA fallback routing, exposes a /health endpoint, enables gzip, applies cache rules for index and hashed assets, and sets security headers including a pinned CSP.

mk7luke and others added 6 commits July 4, 2026 11:00
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CSP: pin sha256 of the app's inline theme/FOUC bootstrap instead of opening
  script-src to 'unsafe-inline', matching the diffsentry.app nginx precedent.
  A future edit to that bootstrap is fail-loud (visible CSP console error),
  caught by the demo smoke.
- Add .dockerignore so 'COPY web/ ./web/' can't overlay the host's
  platform-specific node_modules onto the container npm ci; build context is
  ~6kB and reproducible from source (applies to the main Dockerfile too).
Verified via Playwright on :3027 => 0 console errors, 0 /api calls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@diffsentry

diffsentry Bot commented Jul 4, 2026

Copy link
Copy Markdown

DiffSentry has completed the review — Looks good!

@diffsentry

diffsentry Bot commented Jul 4, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Added a standalone nginx configuration to serve the public demo as a static SPA on its own host. Hardened the demo container with SPA routing, caching rules, security headers, and a pinned CSP.

Changes

Cohort / File(s) Summary
Demo Nginx Hosting
deploy/demo-nginx.conf
Added a dedicated nginx config for the standalone public demo container. It served the fixture-backed SPA with root-level routing, health checks, cache behavior for the shell versus hashed assets, compression, and tightened browser security headers.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant Nginx
    Browser->>Nginx: GET /overview
    Nginx->>Nginx: try_files $uri $uri/ /index.html
    Nginx-->>Browser: index.html with CSP and no-store
    Browser->>Nginx: GET /assets/app-hash.js
    Nginx-->>Browser: cached static asset with immutable cache headers
Loading
sequenceDiagram
    participant Monitor
    participant Nginx
    Monitor->>Nginx: GET /health
    Nginx-->>Monitor: 200 ok
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Suggested Labels

enhancement, security

Risk Assessment

Score: 0/100 — 🟢 Low

No elevated risk signals detected.

🧭 Description Drift

  • ⚠️ Description mentions many files/features that are not present in the provided diff
    • The PR description claims changes to web/src/demo/mode.ts, vite.config.ts, vite-env.d.ts, web/src/main.tsx, Dockerfile.demo, docker-compose.yml, .dockerignore, and a build:web:demo script, but the actual diff shown only adds deploy/demo-nginx.conf. Based on this diff alone, the build-time VITE_FORCE_DEMO flag, redirect-loop guard, container/image setup, compose service, and reproducible-build changes are unsupported.
  • ⚠️ Claim that the demo ships on port 3027 is not reflected in the nginx config
    • deploy/demo-nginx.conf listens on port 80/[::]:80, not 3027. The description says the standalone demo serves on 3027, which may be true via Docker/compose port mapping, but no such mapping appears in this diff.
  • ⚠️ Description says the image ships no server, but the diff explicitly adds an nginx server config
    • The description states the demo artifact ships 'no server, DB, keys, or .diffsentry.yaml', yet this diff adds deploy/demo-nginx.conf, i.e. configuration for an nginx server. If 'no app server/backend' was intended, the wording is misleading relative to the code shown.
  • ℹ️ The nginx config includes caching/compression details not called out in the description's change list
    • deploy/demo-nginx.conf adds gzip on, explicit gzip_types, Cache-Control: no-store at server level, and immutable caching for /assets/. The description mentions SPA fallback, /health, security headers, and CSP, but not these cache/compression behaviors.
  • ℹ️ The nginx config restricts server_name values, which the description does not mention
    • deploy/demo-nginx.conf sets server_name demo.diffsentry.app localhost;. This is a notable host-level behavior in the deployed container but is omitted from the PR description.

Compares PR description claims to the actual diff. Update the description (or the code) so they tell the same story.

✍️ Commit Message Coach

1 of 9 commit messages could be stronger.

Commit Subject Issues
ba41efb 🟡 fix(demo): mark the SPA shell no-store so a stale index.html can't reference gone bundles Subject is 89 characters — keep under 72 to avoid truncation.

Tip: imperative mood (Add user lookup), under 72 chars, no trailing period. Conventional Commits like feat: / fix: are also fine.

🚥 Pre-merge checks | ✅ 5 | ❌ 0
✅ Passed checks (5 passed)
Check name Status Explanation
PR Title ✅ Passed The title meets all requirements: it starts with the imperative verb 'Move', is 59 characters long, has no trailing period, and does not use a Conventional Commits prefix.
PR Description ✅ Passed The PR description meets the stated requirements. It clearly explains what changed (moving the public demo to a standalone static nginx-served artifact with a forced demo build and related routing/deploy changes) and why (so the real instance no longer exposes an anonymous public surface). There is no related issue/PR link included, but the requirement says 'if applicable,' so this is not necessarily blocking based on the provided text.
Schema bump ✅ Passed src/storage/db.ts was not changed in this PR. The PR only adds deploy/demo-nginx.conf, so the schema-versioning check is not applicable and passes.
Provider parity ✅ Passed No changes to src/ai/anthropic.ts are present in this PR, and neither openai.ts nor openai-compatible.ts are touched. This PR only adds deploy/demo-nginx.conf for the standalone demo container, so the Anthropic/OpenAI request-response contract check is not applicable.
Pattern test coverage ✅ Passed No changes in this PR touch src/safety-scanner.ts or src/pattern-checks.ts, so there are no new scanner/pattern rules that require a corresponding tests/e2e/scenarios/ scenario.

✏️ Tip: You can configure your own custom pre-merge checks in your .diffsentry.yaml.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Generate docstrings (beta)
  • Push docstring commit to this branch
🧹 Simplify (beta)
  • Push simplification commit to this branch
🪄 Autofix unresolved comments (beta)
  • Push autofix commit to this branch

Comment @diffsentry help to get the list of available commands and usage tips.

@diffsentry diffsentry Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

This PR is well-structured and the force-demo split is mostly implemented correctly: the demo routing logic, root-basename behavior, and redirect-loop guard all line up with the stated goal. The main issue is in the demo nginx health endpoint, which currently sets Content-Type with add_header on a return response and can end up serving /health as the default application/octet-stream; that makes the endpoint less predictable for health probes and curl-based checks. Aside from that, the changes look cohesive and low-risk.

🧹 Nitpick comments (1)
deploy/demo-nginx.conf (1)

Line 36: The health endpoint does not reliably return a text/plain Content-Type.

⚠️ Potential issue | 🟡 Minor

The health endpoint does not reliably return a text/plain Content-Type.

add_header Content-Type text/plain; is not the right mechanism for setting the response MIME type on a return-generated body in nginx. For literal return 200 'ok'; responses, nginx can still emit its default type, which makes /health less deterministic for probes and contradicts the intent of the config comment.

Set the MIME type with default_type inside the exact-match location before returning the body. That guarantees clients and diagnostics consistently see text/plain for the health response.

🔧 Proposed fix
location = /health {
  default_type text/plain;
  return 200 'ok';
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In deploy/demo-nginx.conf around line 36, change the `/health` location to set the response MIME type with `default_type text/plain` instead of `add_header Content-Type`; keep the `return 200 'ok';` response body unchanged. Make the replacement directly in the exact-match `/health` block so health probes get a deterministic `text/plain` response.
♻️ Suggested fix
location = /health {
  default_type text/plain;
  return 200 'ok';
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In deploy/demo-nginx.conf around line 36, change the `/health` location to set the response MIME type with `default_type text/plain` instead of `add_header Content-Type`; keep the `return 200 'ok';` response body unchanged. Make the replacement directly in the exact-match `/health` block so health probes get a deterministic `text/plain` response.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

In `deploy/demo-nginx.conf`:
- Line 36: In deploy/demo-nginx.conf around line 36, change the `/health` location to set the response MIME type with `default_type text/plain` instead of `add_header Content-Type`; keep the `return 200 'ok';` response body unchanged. Make the replacement directly in the exact-match `/health` block so health probes get a deterministic `text/plain` response.
🪄 Autofix (Beta)

Fix all unresolved DiffSentry comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: .diffsentry.yaml

Review profile: ASSERTIVE

Run ID: d49d0ac3-34e1-404c-a223-72f560b17712

📥 Commits

Reviewing files at 372b027 (base SHA unavailable).

📒 Files selected for processing (10)
  • .dockerignore
  • Dockerfile.demo
  • deploy/demo-nginx.conf
  • docker-compose.yml
  • docs/superpowers/plans/2026-07-04-move-demo-to-static-site.md
  • docs/superpowers/specs/2026-07-04-move-demo-to-static-site-design.md
  • web/src/demo/mode.ts
  • web/src/main.tsx
  • web/src/vite-env.d.ts
  • web/vite.config.ts
✅ Files skipped from review due to trivial changes (1)
  • package.json
✂️ Files with diffs truncated to fit the review size budget (1)
  • docs/superpowers/plans/2026-07-04-move-demo-to-static-site.md

Comment thread deploy/demo-nginx.conf Outdated
@diffsentry

diffsentry Bot commented Jul 4, 2026

Copy link
Copy Markdown

📌 Status — last updated 59b77ad

🟢 Approved

Risk score 0/100 (Low) ███▁
Unresolved threads 0
Failing checks 0
Pending checks 1
Files reviewed 1 (10 skipped)
Updated 2026-07-04 22:24Z

Live-updated by DiffSentry on every push. Use @diffsentry ship for a verdict, @diffsentry timeline for full history.

add_header Content-Type appends a header on top of the one nginx already sets
from default_type (application/octet-stream in the base image) for the return,
yielding a duplicate/nondeterministic Content-Type. default_type sets a single,
correct text/plain. Body 'ok' unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
diffsentry[bot]
diffsentry Bot previously requested changes Jul 4, 2026

@diffsentry diffsentry Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

The nginx demo container is a clean extraction of the public demo surface and the config is generally solid: SPA fallback, immutable asset caching, and a narrowly scoped CSP are all in place. The main thing missing is ensuring the shell document itself is never cached, otherwise deploys can strand users on an old index.html that references no-longer-present hashed assets. I also recommend tightening the health endpoint so it is not rewritten through the SPA location precedence in future edits.

🧹 Nitpick comments (1)
deploy/demo-nginx.conf (1)

Line 34: Place the health check before the generic SPA fallback for defensive clarity.

🛠️ Refactor suggestion | 🟡 Minor

Place the health check before the generic SPA fallback for defensive clarity.

nginx will currently prefer location = /health over location /, so this works as written. The problem is maintainability: the config relies on readers remembering nginx's location precedence, and a later regex or higher-priority location can accidentally change that behavior.

Moving the exact-match /health block above the SPA fallback makes the intent obvious and reduces the chance that future edits turn the health endpoint into an HTML response from /index.html. This is a low-risk cleanup, but worth doing in a deployment-facing config file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In deploy/demo-nginx.conf around line 34, move the exact-match `location = /health` block above the generic `location /` SPA fallback. Preserve the existing response body and status code; this is only to make routing intent explicit and safer against future nginx location changes.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In deploy/demo-nginx.conf around line 34, move the exact-match `location = /health` block above the generic `location /` SPA fallback. Preserve the existing response body and status code; this is only to make routing intent explicit and safer against future nginx location changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

In `deploy/demo-nginx.conf`:
- Line 29: In deploy/demo-nginx.conf around line 29, add an exact-match caching rule for `index.html`; set `Cache-Control` to `no-store` so the SPA shell is always refreshed while `/assets/` remains immutable. Keep the existing `/assets/` caching policy unchanged and place the new block before the generic `location /` fallback.
- Line 34: In deploy/demo-nginx.conf around line 34, move the exact-match `location = /health` block above the generic `location /` SPA fallback. Preserve the existing response body and status code; this is only to make routing intent explicit and safer against future nginx location changes.
🪄 Autofix (Beta)

Fix all unresolved DiffSentry comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: .diffsentry.yaml

Review profile: ASSERTIVE

Run ID: 361e9c9d-d1f5-40bb-9c5e-883cfb9c15ee

📥 Commits

Reviewing files that changed from 372b027 to 9736fbc. Previously-reviewed commits are not re-reviewed.

📒 Files selected for processing (1)
  • deploy/demo-nginx.conf
✅ Files skipped from review due to trivial changes (1)
  • package.json
🚧 Files skipped from review as they are similar to previous changes (9)
  • .dockerignore
  • Dockerfile.demo
  • docker-compose.yml
  • docs/superpowers/plans/2026-07-04-move-demo-to-static-site.md
  • docs/superpowers/specs/2026-07-04-move-demo-to-static-site-design.md
  • web/src/demo/mode.ts
  • web/src/main.tsx
  • web/src/vite-env.d.ts
  • web/vite.config.ts

Comment thread deploy/demo-nginx.conf
Comment thread deploy/demo-nginx.conf
…ference gone bundles

index.html is the Vite release manifest (points at hashed /assets that are
replaced each deploy); a cached shell could request bundles that no longer
exist. Default to Cache-Control: no-store at the server level; the /assets
location overrides it with immutable for the content-addressed bundles. Server
level (not a per-location add_header on the shell) so index.html keeps the
security + CSP headers — a location add_header would drop that inheritance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@diffsentry diffsentry Bot dismissed their stale review July 4, 2026 22:20

Superseded by new review.

diffsentry[bot]
diffsentry Bot previously requested changes Jul 4, 2026

@diffsentry diffsentry Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

The standalone demo nginx config is mostly solid: it serves the SPA with a strict CSP, sensible cache behavior for index.html versus hashed assets, and a simple health endpoint. The main gap is that the /assets/ location overrides inherited headers in nginx, which means hashed JS/CSS responses will likely lose the global security headers and CSP unless they are re-declared there.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

In `deploy/demo-nginx.conf`:
- Line 32: In deploy/demo-nginx.conf around line 32, update the `location /assets/` block so asset responses keep the same hardening headers as the parent `server` block. Re-declare `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy`, and `Content-Security-Policy` inside `/assets/` alongside the asset-specific `Cache-Control` override, because nginx `add_header` in a nested location replaces inherited headers rather than merging them.
🪄 Autofix (Beta)

Fix all unresolved DiffSentry comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: .diffsentry.yaml

Review profile: ASSERTIVE

Run ID: 221b951f-24f2-4dce-9aa5-e19f809e7a05

📥 Commits

Reviewing files that changed from 9736fbc to ba41efb. Previously-reviewed commits are not re-reviewed.

📒 Files selected for processing (1)
  • deploy/demo-nginx.conf
✅ Files skipped from review due to trivial changes (1)
  • package.json
🚧 Files skipped from review as they are similar to previous changes (9)
  • .dockerignore
  • Dockerfile.demo
  • docker-compose.yml
  • docs/superpowers/plans/2026-07-04-move-demo-to-static-site.md
  • docs/superpowers/specs/2026-07-04-move-demo-to-static-site-design.md
  • web/src/demo/mode.ts
  • web/src/main.tsx
  • web/src/vite-env.d.ts
  • web/vite.config.ts

Comment thread deploy/demo-nginx.conf
Defining add_header inside location /assets/ replaces (does not merge) the
inherited server-level headers, so the hashed JS/CSS were shipping without
X-Frame-Options / X-Content-Type-Options / Referrer-Policy. Re-assert them in the
block. CSP is intentionally not repeated: a CSP response header is inert on a
static sub-resource (only the document's CSP governs script/style loads), so
there's no benefit to duplicating the long policy string.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@diffsentry diffsentry Bot dismissed their stale review July 4, 2026 22:24

Superseded by new review.

@diffsentry diffsentry Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

The new standalone demo nginx config is clean and well-documented, with sensible SPA fallback, cache policy separation between index.html and hashed assets, and an appropriately strict CSP for a static fixture-backed app. I did not find any correctness, security, or maintainability issues in the added file based on the diff provided.


ℹ️ Review info
⚙️ Run configuration

Configuration used: .diffsentry.yaml

Review profile: ASSERTIVE

Run ID: 2f3d0f59-3914-444c-8233-d179f9f454d5

📥 Commits

Reviewing files that changed from ba41efb to 59b77ad. Previously-reviewed commits are not re-reviewed.

📒 Files selected for processing (1)
  • deploy/demo-nginx.conf
✅ Files skipped from review due to trivial changes (1)
  • package.json
🚧 Files skipped from review as they are similar to previous changes (9)
  • .dockerignore
  • Dockerfile.demo
  • docker-compose.yml
  • docs/superpowers/plans/2026-07-04-move-demo-to-static-site.md
  • docs/superpowers/specs/2026-07-04-move-demo-to-static-site-design.md
  • web/src/demo/mode.ts
  • web/src/main.tsx
  • web/src/vite-env.d.ts
  • web/vite.config.ts

@mk7luke

mk7luke commented Jul 4, 2026

Copy link
Copy Markdown
Owner Author

@diffsentry release-notes

@diffsentry

diffsentry Bot commented Jul 4, 2026

Copy link
Copy Markdown

📣 Release Notes

✨ What's new

  • The live demo now has its own dedicated home at demo.diffsentry.app. That makes it easier to try DiffSentry without touching a real customer instance.
  • The demo now opens at the site root with cleaner links. You can land directly on demo pages without extra path prefixes, and shared links are simpler to read.
  • The demo remains fully self-contained. It runs from built-in sample data, so exploring it does not trigger real backend activity.

🛠 Improvements

  • Improved reliability for the public demo. It is now packaged and served as a standalone web app, reducing coupling with the main hosted environment.
  • Faster, more predictable demo loading. Static assets are served with caching tuned for quick repeat visits, while the app shell stays fresh on updates.
  • Stronger browser-side protections. The demo is served with tightened security rules and health checks for safer, more stable delivery.
  • Better support for direct links. Opening a saved or shared demo URL now routes correctly within the app experience.

🐛 Fixes

  • Fixed a redirect loop in demo mode that could occur in the standalone demo build.
  • Fixed public demo routing behavior so demo pages load correctly from the root domain instead of depending on a /demo path.

Marketing-speak version of this PR. Re-run with @diffsentry release-notes.

@mk7luke mk7luke merged commit 7995185 into main Jul 4, 2026
4 checks passed
@mk7luke mk7luke deleted the move-demo branch July 4, 2026 22:52
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