Skip to content

feat: landing redesign, anonymous monitoring and public repository setup - #252

Open
szymeo wants to merge 51 commits into
mainfrom
redesign-landing
Open

szymeo wants to merge 51 commits into
mainfrom
redesign-landing

Conversation

@szymeo

@szymeo szymeo commented Sep 5, 2026

Copy link
Copy Markdown
Member

What changed

The landing, docs and SEO pages are redesigned in a lined layout, visitors can start monitoring a URL without an account (anonymous accounts that GitHub or Google login later claims), and the repository is set up as a public open source project: MIT licence, README, CONTRIBUTING, SECURITY, CODE_OF_CONDUCT, issue and PR templates, a frontend check workflow.

Highlights:

  • Hero with a no-signup URL composer over a product-faithful app frame, live demo sections, testimonials, FAQ, footer with the wordmark rising out of the page.
  • One shared nav panel for Product and Resources that unfolds and resizes between menus.
  • Docs and guides in a bounded frame with sidebar and on-this-page column, typed content blocks, SEO families for alternatives, health checks and vs pages, generated sitemap.
  • Pages scroll the document again: reload and back/forward land on the saved offset in the first painted frame in Chromium, WebKit and Firefox.
  • hyper-ui: one neutral ramp, flat surfaces, native styled scrollbars, press feedback. Two lint guards ban translucent greys and easing backgrounds.
  • Backend: POST /users/anonymous, claiming anonymous projects on OAuth login, monitor limits and rate limiting for anonymous users.
  • The whole frontend passes eslint now, and CI runs it.

Why

The previous landing no longer matched the product, signup was the first thing a visitor hit, and the repository goes public under MIT with the docs a public project needs. Self-hosting status is documented honestly and tracked in #251.

How it was tested

  • pnpm --filter frontend check (svelte-check, 0 errors), prettier, both guards, eslint with --max-warnings 0, pnpm --filter frontend build.
  • Playwright e2e: 106 specs (anonymous landing flow, SEO metadata) against the dev server.
  • Backend specs touched by the branch (anonymous auth, monitor writes, pull writes): 37 passing via Docker testcontainers.
  • Reload restoration measured frame by frame with CDP screencast in Chrome (4x and 8x CPU throttle), plus WebKit and Firefox timing samples.

Screenshots

To be attached from the Conductor workspace (.context/validate/reload/hero-1440.png, nav-lines.png, docs-top.png).


  • One logical change
  • Backend behaviour changes have tests
  • Commit messages follow type(scope): summary

🤖 Generated with Claude Code

…claim

Unclaimed anonymous accounts now live 24h instead of 7d, and the token
issued by POST /users/anonymous expires with them, so a reaped user gets
a real 401 instead of a 404 the frontend renders as a 500.

CustomJwtService.sign takes optional JwtSignOptions and the anonymous
endpoint passes expiresIn built from the same env config the reaper
reads, so both move together.

Claiming a temporary account into a new account now requires
termsAccepted, matching sign up. Claiming into an existing account is
unchanged.
Monitor creation is called browser-direct from the landing page, so the client
address is the real one: budget it at 20 requests per minute per IP. Lower the
per-project unclaimed monitor cap from 100 to 3 so a single project cannot be
used to park monitors the pinger then has to keep checking.
The unclaimed pass used to read every project of every tier every 15 seconds
just to build an $in list. Query { claimed: false, mode } straight from the
monitors collection instead, backed by a new { claimed: 1, createdAt: -1 }
index. Claimed monitors keep their tier-based selection.
Claiming a monitor lowers the unclaimed count, so gating it on the unclaimed cap
made no sense; at the new cap of 3 it also became reachable, leaving a project
that holds 3 unclaimed monitors unable to claim any of them. Split the check so
creation keeps both the unclaimed cap and the tier limit while claim only asks
about the tier limit, and restore the ttl spec fixture order that was only
working around this.
The GET and POST bodies depend on the session cookie, so a shared cache in
front of the app could hand one user's token to another. Send Cache-Control
no-store and Vary Cookie on every response.
The oauth state cookie now carries the flow, so the github and google
callbacks can tell a claim from a login without trusting the callback url.
On a claim they probe /users/me with the session cookie, and only an
anonymous user is claimed through POST /auth/{provider}/claim; the returned
token replaces the cookie and the user lands on the dashboard. A 409 sends
them back to the claim form with the project limit banner and keeps the
temporary session alive.

Anonymous users hitting an upgrade are routed to the claim form with their
tier, which the callback restores so the stripe redirect still runs.
A 5xx or a timeout on the /users/me probe used to fall through to the login
flow, which overwrites the anonymous token cookie and leaves the user in a
different account with their dashboard unreachable. An unavailable probe now
sends them back to the claim form with the session cookie untouched, and only
a genuine unauthorized or missing user still falls back to login.

Every other claim failure, including a cancelled consent with no code, a burnt
code and an unusable claimed token, also returns to the claim form with an
error code instead of the sign up screen whose primary button would overwrite
the same cookie. Any claim failure clears the onboarding tier so a still
anonymous user is not sent to stripe checkout.

Also moves the token max age helper next to decodeJwtPayload and reuses it in
the session bff.
…ards

Anonymous users now see a persistent claim banner with a live countdown
on every cluster page, a one-click /app/quick-setup entry point that
skips straight to a temporary dashboard, and a claim CTA on the metrics
tile once the free-tier metrics limit is hit. Claiming an account from
any of these surfaces now round-trips back through claimed=1 so the
account_claimed analytics event actually fires, and the layout no
longer crashes when that param is present on the very first page load
(replaceState was being called before the SvelteKit router finished
initializing). Also removes the vestigial anonymousProject preload
that nothing consumed anymore, and collapses the quick-setup link in
AuthForm now that the route exists.
…uplicate

Push mode created an unclaimed monitor as soon as the name was valid,
then finishing setup created a second monitor and claimed only that
one, leaving the first behind. With the 3-unclaimed-monitor cap this
reliably 409s after a few attempts. Finishing now claims the pending
monitor directly (updating its name first if it changed), and pull
mode is unaffected. A 409 during setup now shows a message about too
many monitors waiting to be claimed instead of a generic failure.

Also fixes claimMonitor storing the claim endpoint's empty response
body under the monitor id, which made the UI immediately request
/monitors/undefined/http_ping_buckets; it now refetches the monitor
from the project's monitor list. Dropped the unused limit parameter
on loadPingBuckets, which the ping-buckets endpoint never accepted.
A 401 from /users/me was never reaching the expired-session redirect:
logdashAPI's unauthorized handler throws its own SvelteKit redirect
before the caller's error message can be inspected, and that thrown
Redirect was not an Error instance so the existing string-based check
always missed it, silently bouncing the user to a bare /app/auth with
the stale cookie still set. isExpiredSession now also recognizes a
thrown redirect. Separately, the preload method crashed outright
(this was undefined) because resolve_data_preloader hands callers a
detached method reference; isExpiredSession is now a plain function
instead of relying on `this`.
Replace the Business Source License with MIT (both co-authors agreed),
set the licence field in every package and rename the backend package
from the starter template name.

Add the repository front door for a public project: README with the
product, the honest self-hosting status (tracked in #251), local
development steps and contribution pointers; CONTRIBUTING, SECURITY and
CODE_OF_CONDUCT; issue and pull request templates; a dev compose file for
Mongo, Redis and ClickHouse; root scripts for dev, build, check, test and
format; a pinned node version; and .context/ ignored so agent scratch
directories never get committed.
Editor-specific config is gone. The frontend and backend rules and the
component cleanup command live in .agents/, indexed by a root AGENTS.md
that Cursor, Codex and Copilot pick up on their own.
…de 22

The frontend workflow runs svelte-check, prettier, the solid colour and
background transition guards and a production build. Backend actions
move to node 22 and re-run when the shared templates change.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 5, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
logdash-io 7ba5f5c Commit Preview URL

Branch Preview URL
Sep 05 2026, 09:41 AM

One OKLCH-derived neutral ramp with a hairline token, flat surfaces,
daisyUI components narrowed so backgrounds never ease, native scrollbars
styled through scrollbar-width and scrollbar-color, a ScrollArea that is
a plain native scroller, press feedback for buttons, and a Tooltip that
positions its fixed element without adding the window scroll offset.
Public dashboard components pick up the same tokens.
…ross the app

Translucent greys (text-*/NN, bg-*/NN, opacity tints) become solid steps
of the neutral scale and background colours no longer ease on hover,
focus or selection; only ink, borders and transforms transition. Cards,
tiles, sidebars, settings, the logs tile, auth screens and the shared
components follow the same flat direction as the landing.
A Linear-style lined layout: hero with a no-signup URL composer over a
product-faithful app frame on a lit stage, live demo sections (log tail,
metrics, downtime feed), first-thirty-days, testimonials switcher, FAQ,
attio-style footer with the wordmark rising out of the page. Product and
Resources open one shared nav panel that unfolds and resizes between
menus. Docs and guides sit in a bounded frame with a sidebar and an
on-this-page column, fed by typed content blocks; SEO families cover
alternatives, health checks and vs pages, with a generated sitemap.

Pages scroll the document, so the browser restores scroll before the
first paint; app.html blocks rendering until the document is parsed and
restores the saved offset at parse end for engines that would wait for
load. Only the article fades between docs pages; same-page anchors scroll
smoothly. Inter replaces Kumbh Sans. Two lint guards ban translucent greys
and easing backgrounds; Playwright specs cover the anonymous landing flow
and SEO metadata.

Fabricated social proof is gone: the incidents odometer placeholder and
the logo wall were removed, the eyebrow names the three founders whose
testimonials appear on the page, and the downtime feed's posts are marked
as invented. Self-hosting docs and README link the tracking issue #251.
The app carried 128 eslint errors and 39 warnings that kept eslint out of
the pull request workflow. Every {#each} is keyed, internal navigations go
through resolve() from $app/paths with typed route ids, useless children
snippets and mustaches are unwrapped, unused imports, variables and
parameters are gone, `any` icon props are typed as Component, dependency
reads in effects are explicit `void` expressions, empty catches return,
and a write-only polling map is deleted. The remaining eslint-disable
directives each carry a reason: our own static legal HTML, a caller-
supplied href and an absolute status page URL from state.

CI and `npm run lint` now run eslint with --max-warnings 0.
…ites spec

Logs are queued and flushed by a once-a-second cron whose insert is fire
and forget, so a fixed 1.5 s sleep before reading them back raced on a
slow CI runner: "creates multiple logs in batch" saw 0 of 3 logs. A
waitFor helper polls ClickHouse every 300 ms until the expected rows are
there (10 s cap) and returns the last read so a real failure still shows
what was found. The four waits in this spec use it.
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