Changelog

What we shipped, when we shipped it.

Product updates from the Solagon team — new features, redesigns, and fixes across solagon.com, the client portal, and our subdomain platform.

  1. Site-wide audit sweep — readable blog, security hardening, faster status pages, lighter homepage

    A top-to-bottom audit of the marketing site, client portal, and admin dashboard surfaced around 40 issues across visuals, performance, and security. This release fixes the substantive ones.

    • The blog reads like a document again. The entire article + case-study body was rendering unstyled — headings at body size, no paragraph spacing, bullets stripped — because the Tailwind typography plugin every long-form page depends on was never installed. Installed it and wired a per-theme prose treatment, so every /articles/* and case-study narrative (the whole SEO content push) now has real hierarchy and rhythm.
    • Search no longer fed two 404s and three dead redirects. The sitemap was submitting /industries/professional-services and /industries/saas (empty directories), and three /services/development/* redirects pointed at a /solutions/full-stack-development page that doesn't exist. Removed the phantom sitemap entries, repointed the redirects to a real page, 301'd the duplicate /insights/:slug blog route onto the canonical /articles/:slug (and deleted the broken pre-redesign version), and added /changelog to the sitemap.
    • Tighter access controls across the dashboards. Every staff dashboard API now enforces per-client, org-scoped permissions, so team members only reach the clients they're assigned to while super-admins retain full visibility.
    • Hardened public endpoints. Added rate limiting and input-size caps on the AI chat and booking endpoints, brute-force protection on the sign-in and preview-unlock flows, and stricter file-upload validation.
    • Status pages stopped getting slower every week. They were pulling up to ~26,000 raw uptime-check rows per site on every single view and bucketing them in JavaScript. Replaced that with two SQL aggregate queries, cached per-site at the 5-minute cron cadence, and added a retention job so the checks table self-prunes to 90 days.
    • Security headers + a lighter homepage. Added a baseline of security headers site-wide (HSTS, nosniff, clickjacking + referrer protections). Cut the homepage's screenshot payload from ~4.5 MB of PNG to ~2 MB by converting the full-page captures to WebP. Removed the unused three.js dependency tree (~1 MB) and six dead 3D components that rendered nothing, plus nine unreferenced leftover files from the reverted 'Solagon OS' dashboard rebuild — the production build is green again.
    • Polish across the board. Fixed the keyboard focus ring being invisible in dark mode; gave the whole site a branded social-share image (every page had a blank card before); added a branded error page; fixed literal ’/\u2014 text bugs and a missing CSS variable that left surfaces transparent on ~9 pages; brought the comparison page onto the brand system; made the portal sign-up link visible; added the missing /admin/clients, /admin/portal, and /book/cancel pages that were dead-ending on the public 404; and pointed every 'Book a call' button at the actual scheduler instead of the contact form.

    Solagon Team

    solagon.com

  2. Marketing site QA pass — booking page, careers refactor, 404 polish, SEO health fixes

    Audit-driven sweep of the marketing surface. Two parallel audits (Ahrefs site-health + a hard read of every page) surfaced concrete issues; the highest-impact ones are below. Solagon brand orange preserved throughout.

    • Ahrefs site health was 71% (lowest of every Solagon-managed project). Top contributors: 100 internal 404s + 85 internal links to broken pages, 197 missing alt-text warnings, 42 long meta descriptions, 38 orphan pages. Removed two conflicting redirects in next.config.js (/industries/saas and /industries/professional-services were silently 308'ing to /industries despite real pages existing at those URLs). Added both pages to sitemap.ts so Google can find them. Added /booking to the sitemap too.
    • New `/booking` page — conversion-focused, separate from /contact. Hero with primary CTA above the fold, three-up agenda strip ('what we'll cover'), trust strip, list of bookable meeting types pulled live from book.solagon.com's DB, and a six-question FAQ that pre-empts the objections that block bookings. Rewired the nav CTAs (desktop + mobile drawer) from /contact/booking so 'Get started' actually goes to the conversion path.
    • Careers restructureddata/careers.ts four pillars are now Software developers / Web developers / App developers / AI specialists (was Engineering / Design / Growth / Strategy). Each pillar has new copy framed for the right audience: software devs get TypeScript/Drizzle/Vercel signals, web devs get Lighthouse/CWV signals, app devs get App Store + OTA pipeline signals, AI specialists get RAG/eval/cost-ceiling signals. Updated application questions per pillar and the 'Ownership' bullets in <DisciplinesExplorer> to match.
    • `/not-found.tsx` polished — bigger mono 404 mark anchored above the headline, soft accent glow + subtle grid mesh on the background, suggested destinations regrouped into a 2-up icon grid (Home · Case studies · Book a call · Insights · Careers · Support) with per-row icons for affordance. Tokens-driven so it tracks light + dark. Robots set to noindex,nofollow so the 404 doesn't compete with real pages in search.
    • 'The math after we hand it over' rebuilt — surfaces the detail field that was set in the data model but never rendered (so '$27k' was a bare number; now it reads '$27k · Saved in year one · Median client savings · roughly $2,268 / month after a rebuild'). Added a tiny mono ordinal per card, an accent underline that sweeps in on scroll, and a hover accent-wash. Tightened all six labels with concrete specifics — '5.8× faster page loads · From 2.3s down to 0.4s' instead of just '5.8×'.
    • Sitemap correctness — every reachable page now lives in app/sitemap.ts, no redirects shadowing real pages, no orphans for the routes the audit caught. Should drop the 'orphan page' count + the 'pages have links to broken page' count substantially on the next Ahrefs crawl.

    Solagon Team

    solagon.com

  3. QA pass · honest fixes — theme correctness, error boundaries, report exports

    Pass two of the redesign brief: a hard, audit-driven sweep of theme correctness, navigation state, and the missing affordances staff + clients actually need. Two parallel audits surfaced concrete issues; the highest-impact ones are fixed below. The honest summary is in the inline callout — what was actually done vs what still needs hands-on verification in a browser.

    • Uptime cron back to 5 minutes in vercel.json per the latest instruction. Default check_interval_minutes on monitored_sites is back to 5; CronFreshnessBanner stale-threshold dropped from 75min back to 15min (3 missed ticks at 5-min cadence). Docs (STATUS-SUBDOMAIN.md, admin-docs.ts) updated to match.
    • Theme FOUC eliminated: AppShell's useState initializers for theme + collapsed now read localStorage synchronously on the client, so the very first render reflects the user's choice instead of flashing the default and then flipping in a useEffect. Suppressed the corresponding hydration-attribute warning on the wrapper since the diff is intentional and cosmetic only (CSS variables resolve correctly either way).
    • Hardcoded color leaks fixed in three in-app components: <StorageResyncButton> rebuilt on the standard <Button> (was hex-coded orange/white that broke in light mode), <TasksBoard> priority dots now use dash.errorFg/warnFg/infoFg/textTertiary/borderStrong instead of Tailwind bg-red-500/orange-500/blue-500/zinc-400/zinc-600, <NotificationsForm> toggle thumb uses dash.textInverted so it stays high-contrast against both "on" and "off" tracks regardless of theme.
    • Light-mode borders nudged darker (#E5E5E5 → #E0E0E0, #DCDCE0 → #D4D4D8, #C7C7CC → #BFBFC4) so cards on the #FAFAFA page bg have crisp definition without feeling heavy. Old contrast was ~1.3:1 — technically legal as a decorative line but card edges literally disappeared on retina screens.
    • Auth pages deliberately left as-is: /admin/login, /forgot-password, /reset-password are a marketing-grade dark splash (separate route group from the dashboard, no AppShell). Making them theme-responsive would wreck the design intent. Flagged in the audit but kept by choice.
    • Monthly report exports — net-new feature you asked for. PDF via the browser's native print-to-PDF (controlled with a @media print stylesheet that hides the sidebar / sticky topbar / .print-hidden chrome and keeps card silhouettes intact across page breaks; better fidelity than any server-side PDF pipeline we'd ship without a heavy headless-Chrome dep). CSV via new /api/portal/reports/[id]/export?format=csv — streams a spreadsheet with one row per metric (label · current · previous · % change · unit) plus the analysis markdown appended. <ReportDownloadActions> button cluster wired into both the portal-side and admin-side report detail pages.
    • Error boundaries at three segments (app/admin/(dashboard)/error.tsx, app/admin/(dashboard)/clients/[slug]/error.tsx, app/portal/[slug]/error.tsx) — no more unstyled Next default error page when a server component throws. Each renders a recoverable surface with retry + safe fallback link, logs the error to console, and exposes the digest so engineering can correlate.
    • Misleading "Open" spans removed from all 8 business-record list pages (4 team-side at /admin/operations/{...}, 4 per-client at /admin/clients/[slug]/{...}). They rendered as gray text with an arrow that *looked* clickable but did nothing — flagged by the audit. Detail pages don't exist for these resources yet; orgs route via the client column instead.

    Solagon Team

    solagon.com

  4. Admin + portal redesign · pass one — Operations pillar, premium primitives, and a test client to drive it all

    First slice of the premium SaaS-OS redesign. Net-new financial layer (Proposals · Retainers · Invoices · Agreements) on both admin and portal sides, foundational primitives that the rest of the redesign builds on (slide-over panel · detail header · detail layout with right rail · quick-create menu), and a Black Arrow test client you can seed in one command to see every surface populated with realistic data. Solagon brand orange stays — every status badge, gradient fill, and active state still inherits the same accent token.

    • Foundational primitives in components/dashboard/: <SlideOverPanel> for inline detail/edit without leaving a list, <DetailHeader> for resource-page identity strips (mark · eyebrow · title · status chips · meta · primary actions), <DetailLayout> with a sticky right rail (Attio pattern: facts + outstanding items + related links), and <QuickCreateMenu> — the grouped "+ New" dropdown wired into the AdminShell footer next to the bell.
    • New financial / commercial layer: four resource types (portal_proposals, portal_retainers, portal_invoices, portal_agreements) added to the Drizzle schema with status enums + indexes. Runtime DDL bootstrap in lib/portal/business-records-schema.ts mirrors the existing client-features pattern so no migration is needed. Read helpers in lib/portal/business-records.ts cover listProposals/Retainers/Invoices/Agreements, scoped lookups by id, plus aggregate helpers (getMonthlyRecurringRevenue, getOutstandingInvoiceTotal).
    • Admin Operations pillar in the team sidebar with four list pages (/admin/operations/proposals/retainers/invoices/agreements) and an index page (/admin/operations) showing MRR, outstanding balance, open proposals, and awaiting-signature counts. Each list is a Stripe-style resource table — KPI strip across the top, status badges, currency-formatted amounts, sort by most-recent.
    • Per-client business-record tabs under a new Operations pillar in every client workspace: /admin/clients/[slug]/proposals/retainers/invoices/agreements. Same patterns, scoped to the org. Populated for the test client; empty + on-brand for new orgs.
    • Client portal redesign: nav restructured into pillars (Performance · Work · Account) instead of a flat list, with four new pages — /portal/[slug]/updates (read-only changelog stream), /portal/[slug]/support (ticket queue with link out to support.solagon.com for new tickets), /portal/[slug]/billing (active retainer card + outstanding balance + invoice history), /portal/[slug]/agreements (signed MSAs / SOWs with links to the document). Existing Overview + Reports + Services pages keep their data; just gain the new sidebar IA.
    • `PortalShell` now accepts pillared `navSections` alongside the legacy flat navItems, so the same shell powers the Linear-style grouped sidebar without breaking any other consumers. Collapse-per-section persistence carries over from the admin sidebar work.
    • `scripts/seed-test-client.mjs` creates a Black Arrow (blackarrow.co) test client end-to-end: organization, monitored site (with 24h of synthetic uptime checks), three sample contact submissions, three support tickets across statuses, a ready-to-review monthly report, an active growth retainer with renewal date, three invoices (paid / sent / past due), two proposals, and a signed MSA + an awaiting-signature SOW. Idempotent — re-running upserts safely. npm run seed:test-client to invoke.
    • Dev-server cold start fixed (separate but adjacent): cleared a 948 MB bloated webpack cache + 51 MB stale build dir from .next, capped the cache going forward via next.config.js, added a dev:turbo script for the Turbopack alpha, excluded .next_stale_* from tsconfig so the IDE stops walking dead trees, and installed two missing transitive type deps (@types/draco3d, @types/offscreencanvas).
    • Brand orange preserved everywhere — accent token unchanged. Every chart gradient, status badge tone, active sidebar item, and inline pill still reads as Solagon orange. The redesign added structure and primitives, not a new palette.

    Solagon Team

    solagon.com

  5. Sidebar sections collapse like Linear (and remember your choice)

    Every labelled pillar in the admin sidebar — Health, Growth, Work, Studio, Stack, Account on the team side; Health, Growth, Work, Settings inside a client workspace — is now collapsible. Click the header chevron to fold the items underneath; the choice persists per-section to localStorage so a workspace you've trimmed down stays trimmed down on reload.

    • Click the section header to toggle. The chevron rotates 90° when expanded; the items animate height + opacity in 200ms so the layout doesn't yank when sections fold and unfold.
    • Per-section persistence via localStorage keys like solagon-sidebar-section:health (slugified from the section label). Collapsed state survives reloads, route changes, and workspace switches; the choice is per-section, not per-sidebar, so you can collapse the pillars you don't use without affecting the others.
    • Always-visible Overview row at the top of every sidebar isn't collapsible — it has no label, so there's nothing to fold under. Sections without a label and without an explicit id automatically opt out of the collapse affordance.
    • Icon-rail mode unchanged: when the whole sidebar is collapsed to its 64px icon rail, section labels are already replaced by hairline dividers. The chevron-toggle pattern only kicks in when the sidebar is expanded.
    • Type-level opt-out via collapsible: false on a section. Default is true for any section with a label — matches Linear's "every group can be folded" model rather than "opt-in per group."

    Solagon Team

    solagon.com

  6. Notifications can be dismissed; monthly reports now live in every client workspace

    Two related housekeeping fixes. The notifications bell got an X on every row plus a Clear-all button, so the popover stops piling up old items. And every client workspace now has a Monthly reports tab — list, edit, send, with an on-demand Generate-draft button — so staff don't have to bounce out to the team-wide reports view to work on a single client.

    • Notification dismissals — new portal_notification_dismissals table (runtime DDL) keyed by (user, notification_key) records what each staffer has X'd. The GET /api/admin/notifications endpoint subtracts dismissed keys before responding. New POST /api/admin/notifications/dismiss accepts a { keys: string[] } body so the same endpoint serves both single-row X clicks and Clear-all (the client just sends the full visible list). Dismissals are durable across reloads and devices for Clerk-authed staff; the legacy JWT login shares a single __jwt__ slot since it's a single shared account.
    • Optimistic UI in the bell popover — clicking X on a notification removes it from the list immediately while the dismiss POST flies in the background. If the request fails the optimistic state still holds for the current session; the next refresh corrects any drift from the server-side truth.
    • Per-client Monthly reports tab at /admin/clients/<slug>/reports. New sidebar item under the Activity group (only renders for client workspaces — Solagon's team sidebar is unchanged). The list page shows every report for that client, regardless of status (draft / in_review / ready / sent / archived) with the month, status badge, last-updated, and sent date. Clicking through opens /admin/clients/<slug>/reports/<id> which mounts the same <ReportEditor> the team-wide surface uses, but with breadcrumbs that trace back through the client's own reports list.
    • On-demand draft generator — the new "Generate draft" button in the client reports header POSTs to /api/admin/clients/<slug>/reports/generate, which calls generateMonthlyReport(orgId) directly so staff can spin up a previous-month draft without waiting for the next monthly cron tick. Same idempotent contract: re-running for an existing month refreshes the metrics snapshot but never clobbers a staff-written analysis.
    • Cron now skips Solagon in generateMonthlyReportsForAllOrgs (WHERE slug != 'solagon'). We don't write monthly SEO reports to ourselves, so the team org no longer accumulates unwanted draft rows on the 1st of each month.
    • Defensive scoping on the per-client editor/admin/clients/<slug>/reports/<id> 404s if the resolved report's organizationId doesn't match the slug's org. Forged URLs like /admin/clients/bluefin/reports/<acme-report-id> can no longer accidentally surface another client's data under the wrong workspace.

    Solagon Team

    solagon.com

  7. Speed insights now shows full audit history (and stops losing past runs to URL normalization)

    Audits were saving correctly but every visit said 'No audits yet.' Root cause: the save path canonicalised the URL (`new URL(target).toString()` adds a trailing slash for bare domains, so `https://solagon.com` becomes `https://solagon.com/`), but the read path passed the raw env var unchanged — so `WHERE url = 'https://solagon.com'` never matched stored rows keyed under `https://solagon.com/`. Fixed by normalising on both sides AND adding a proper history table on the page.

    • `normalizeAuditUrl()` in `lib/portal/integrations/pagespeed-store.ts` is now applied on every write (saveSpeedAudit) AND every read (getLatestSpeedAudit, listRecentSpeedAudits, the new listSpeedAuditHistory). Both sides go through the same new URL().toString() canonicalisation — bare domains pick up the trailing slash, ports / userinfo / hash get stripped, casing on the host gets normalised. No existing data needs to migrate; rows were already saved canonically by the run route, the bug was always on the read side.
    • New `<SpeedAuditHistory>` card beneath the trend chart on both the team and client Speed insights pages. Renders the last 50 audits as a chronological table — strategy badge, Lighthouse score with tone-coloured pill, LCP / INP / CLS metrics, and absolute + relative timestamps. Failed runs show an inline 'Failed' chip with em-dashes for metrics so a botched fetch doesn't disappear silently.
    • `listSpeedAuditHistory(url, limit)` is the new store helper backing the table — combined mobile + desktop, newest first, only the columns the table needs (no field-data, no error message rehydration) so the row size stays small.
    • Why this presented as 'no history': the trend chart only renders when there are 2+ audits per strategy, and the rest of the page renders 'No audits yet' when neither mobile nor desktop returns a row. Both gates required the URL match to succeed, so a single normalisation mismatch wiped the entire page out — even though stored rows were sitting right there in portal_speed_audits.

    Solagon Team

    solagon.com

  8. Uptime checker now runs every 30 minutes (down from 5)

    The 5-minute cadence was aggressive for what we actually need — site outages are usually multi-minute affairs and a 30-minute probe still catches them, but with a fraction of the cron load + email noise + transient false positives. Bumped the cron tick, the per-site default interval, and the staleness banner threshold so they all line up.

    • `vercel.json` cron schedule changed from */5 * * * * to */30 * * * * — the uptime checker fires twice an hour instead of twelve times. Triggers a fresh deploy to take effect (Vercel Cron schedules are read at deploy time, not on the next tick).
    • Default `check_interval_minutes` on `monitored_sites` changed from 5 to 30, so newly added sites match the cron cadence instead of getting starved by it. Existing sites keep whatever interval they were configured with — the cron is the rate limit either way, so a site set to 5 min still effectively checks every 30 min.
    • Staleness banner threshold changed from 10 minutes to 75 minutes. Allows for ~2.5 missed cron ticks before flagging the cron as stale on the Uptime tab — keeps the warning meaningful without false-alarming on a slow Vercel deploy or one cold-start retry.
    • Copy updated in the CronFreshnessBanner chrome, the admin overview's Site uptime card, the admin docs, and STATUS-SUBDOMAIN.md so every reference to "every 5 minutes" now says "every 30 minutes."
    • Heads up on Vercel plan limits: Vercel Hobby caps cron at one tick per day regardless of schedule. If checks still aren't running after the deploy, that's almost certainly the issue — you'll need Pro or higher for sub-daily crons.

    Solagon Team

    solagon.com

  9. Switching back to Solagon now clears the sticky pin instead of snapping back

    Picking 'Solagon' in the workspace switcher fired the dashboard correctly but the trigger button itself kept showing the previous client. The earlier `liveCookieSlug ?? initialActiveSlug` fallback still surfaced the stale SSR-frozen slug whenever the live cookie went null — and clearing the sticky pin sets the cookie to null. So the SSR fallback won, the chrome lied, and the user saw 'Bluefin' on Solagon's data.

    • Live cookie is authoritative on the client. Replaced liveCookieSlug ?? initialActiveSlug with a hard branch: on the client (typeof document !== 'undefined'), the live cookie is the only source of truth. Empty cookie means 'no sticky pin' — full stop, no fallback. initialActiveSlug only feeds the server-render pass so the SSR'd HTML is correct on hard refresh, then it's discarded.
    • Why the previous fix wasn't enough. Adding router.refresh() on switch invalidated the cache for the current URL but couldn't help here: navigating from /admin/clients/bluefin to /admin fetches /admin's layout RSC, and that RSC could have been cached from an earlier render where the cookie was set to bluefin — so its initialActiveSlug was still bluefin. The ?? operator handed that back when the live cookie was null, defeating the click that just cleared the pin.
    • Behaviour now: clicking Solagon clears the cookie, the next render reads null from the live cookie, the sticky fallback returns nothing, and the trigger correctly renders the team workspace. Clicking a client (Bluefin → other client) still updates via the URL match and writes the cookie. Hard refresh on a client URL still SSRs correctly because the cookie is set in the request and initialActiveSlug carries it through the first render.

    Solagon Team

    solagon.com

  10. Premium charts across the admin dashboard — gradients, hover crosshairs, sparklines

    Every chart on the admin dashboard moved off Tremor's high-level wrappers and onto a thin in-house layer built directly on Recharts. The visual lift comes from four small things: per-series gradient fills, a hover crosshair with a precise tooltip, axis ticks tied to the design tokens, and tiny sparklines now available on every KPI card. Tremor itself is gone from the bundle.

    • New chart primitive layer at `components/dashboard/charts/` — three reusable components (<DashAreaChart>, <DashLineChart>, <DashSparkline>) plus a shared tooltip primitive and a chart-specific token set. Every wrapper accepts a series[] config so callers describe what to plot in terms of data shape, not Recharts internals — a future swap to a different charting library is a single-file change.
    • Per-series gradient fills with `useId`-derived ids — gradients fade from each series' colour at 40% opacity to transparent, giving area charts depth without competing with the underlying card chrome. Multiple charts on the same page no longer collide on SVG <defs> ids.
    • Hover crosshair + custom tooltip composition — every chart now draws a dashed vertical line at the cursor's x and a coloured dot at each series' active point. The shared <DashTooltipShell> + <DashTooltipRow> primitives let individual charts (deploys, builds, uptime) compose tailored tooltips that still feel like part of the same product.
    • Migrated charts: DeploymentsTrendChart (Logs deploy cadence), BuildDurationChart (Logs build duration), UsageDeployCadenceChart (Usage page), SubmissionsTrendChart (Analytics), SpeedTrendChart (Speed insights mobile vs desktop), SpeedComparisonChart (replaced Tremor's BarList with a token-driven horizontal bar list), UptimeChart (response-time area), and the portal-side TrafficChart. Each chart's empty state moved into the primitive's emptyState prop so the layout stays stable when there's no data.
    • Sparklines on KPI cards. <MetricCard> now accepts a spark prop (a flat number[]) and renders a 32px gradient sparkline under the headline value. Auto-tones based on slope — green when up, red when down — with invertSparklineTone for "lower is better" metrics like response time. Wired into the Logs page's *Deploys · 14d* and *Median build* tiles as a concrete pattern to extend across other KPI strips.
    • `@tremor/react` removed from the dependency tree along with its optimizePackageImports entry in next.config.js; recharts added as a direct dep so version pinning is explicit rather than transitive.

    Solagon Team

    solagon.com

  11. Admin dashboard navigation now shows an instant loading skeleton

    Clicks through the admin dashboard felt stuck — Next.js was holding the previous page visible until the next page's server render finished, so navigation looked like "did anything happen?" until the full content appeared. Added route-level `loading.tsx` files so a click immediately replaces the page with a skeleton of the new route, plus a small spinner in the TopBar so there's always a "yes, something is happening" cue.

    • Generic dashboard skeleton at app/admin/(dashboard)/loading.tsx — shows the TopBar silhouette (breadcrumb placeholders + spinner), a PageHeader silhouette (eyebrow + title + description), and a stack of card-shaped placeholders. Triggered on every navigation into a /admin/* route while the new page does its server-side render. Falls back to the previous route's content automatically if the new render finishes within ~100ms (Next.js's React 18 transitions handle this — the skeleton only flashes for renders slow enough to actually need it).
    • Wider, denser skeleton at app/admin/(dashboard)/clients/[slug]/loading.tsx — same pattern but tuned for the data-heavy client pages (Logs, Usage, Analytics, Tasks). Wider container + 4 cards instead of 3 so the silhouette matches what's about to render.
    • Why this matters: in dev (and on cold lambdas in prod), pages like Insights → Logs do a Vercel API roundtrip + DB queries before the first byte, which can take 600-1500ms. Without a loading boundary the user sees the OLD page until the new one is ready — feels stuck. The loading.tsx pattern is the canonical Next.js fix: the route is wrapped in a Suspense boundary so the page swap is instant, and the data fetch happens in the background while the skeleton sits there.
    • Shared `<DashboardLoadingSkeleton>` primitive at components/admin/DashboardLoadingSkeleton.tsx — accepts width (narrow / default / wide) and cardRows so individual route segments can opt into a tighter or denser placeholder if needed later. Animates with Tailwind's animate-pulse on every bar so the placeholders feel alive instead of frozen.

    Solagon Team

    solagon.com

  12. Workspace switcher trigger now stays in sync with the active client

    Switching clients via the workspace switcher updated the page content correctly but the trigger button itself sometimes kept showing the previous client. Root cause: Next.js' Router Cache served the prior layout's RSC payload (with its frozen `initialActiveSlug` + `workspaces` props) on the next render, so AdminShell's sticky-fallback logic could fall back to the old client. Fixed by busting the cache on workspace switch and preferring the live cookie over the SSR-frozen value.

    • `router.refresh()` on every workspace pick — the WorkspaceSwitcher's onPick handler now invalidates the cached dashboard layout RSC immediately after writing the sticky cookie. The Link still does the URL change; the refresh ensures the next render of AdminShell reads fresh server-side props (workspaces array + SSR'd cookie value) instead of the cached prior render's.
    • Live cookie beats SSR-frozen value in the sticky fallback. Previously initialActiveSlug ?? readWorkspaceCookie() ran SSR-first, which meant a freshly-written cookie was ignored if the SSR'd value was non-null. Reversed to liveCookieSlug ?? initialActiveSlug — the SSR value is now only used when document isn't available (initial server render or cookies disabled).
    • Why this presented as "the menu in the top right doesn't update": clicking a client routed correctly (URL match wins for the page content), but if the cached workspaces array didn't include the new client (e.g. recently-added membership) the switcher fell through to its sticky fallback, which then used the SSR-frozen initialActiveSlug and proudly showed the previous client.

    Solagon Team

    solagon.com

  13. Per-client pickers for every integration: GitHub, Linear, Cloudflare, Ahrefs

    Following the Vercel project picker, the same pattern now covers the rest of the integrations that need per-client scoping. Each one auto-detects from slug or website URL by default; if the auto-detect doesn't find the right project / repo / team / zone, you can pin one explicitly on the Client details page. Tokens still live once-per-staffer in user settings → integrations; the per-client config layer just answers "which thing inside that token's scope is this client?"

    • GitHub repo picker — lazy-loads /user/repos (sorted by most-recently-pushed so it surfaces what you're actually working in), filters client-side, persists owner/name. Also stamps a "matches slug" hint so the right repo jumps out for clients whose repo name == slug. New endpoint: GET /api/admin/github/repos. Resolver at lib/portal/integrations/github.ts handles override → slug-match fallback for any future consumer (PR feeds, GitHub-fed changelog).
    • Linear team picker replaces the old freetext team-key input — operators now pick from the teams their Linear API key can see, and we persist both the short key (display + URL prefix) and the team UUID (stable across renames). Avoids the case where a typo silently broke the Tasks tab. New endpoint: GET /api/admin/linear/teams. Tasks tab continues keying off linearTeamKey so no consumer changes were needed.
    • Cloudflare zone picker — lists every zone the staff token has access to. Auto-detect derives the apex hostname from websiteUrl (so app.acme.com still matches the acme.com zone) and stamps a "matches website" hint in the list. Persists both cloudflare_zone_id (stable) and cloudflare_zone_name (display). New endpoint: GET /api/admin/cloudflare/zones. Resolver at lib/portal/integrations/cloudflare.ts.
    • Ahrefs domain picker is a hybrid: a free text field (since Site Explorer accepts arbitrary domains) plus a popover that pulls saved Ahrefs projects from /management/projects so you can one-click pick an existing tracked domain instead of re-typing it. Auto-detect derives from website host. Persists a normalized hostname (no scheme, no trailing slash). New endpoint: GET /api/admin/ahrefs/projects.
    • Schema additions (runtime DDL — no migration): portal_organizations.github_repo, linear_team_id, ahrefs_domain, cloudflare_zone_id, cloudflare_zone_name. All nullable; null means "auto-detect". Drizzle types updated alongside.
    • Single Integrations card on Client details replaces the old Workflow card — five pickers (Vercel, GitHub, Linear, Cloudflare, Ahrefs) one above the other, all sharing the same trigger / popover / Clear-override visual language so the form reads as a unit. The PUT endpoint accepts each new field with consistent normalization (trim, lowercase host, drop scheme).

    Solagon Team

    solagon.com

  14. Pick a client's Vercel project manually when slug-match doesn't find one

    Logs, Usage, and Changelog-sync used a single heuristic to find a client's Vercel project: match the org slug against project names. When the project was named differently (legacy clients, multi-project setups, post-rename), every dependent surface fell back to its empty state. The Client details form now has a Vercel project picker — pick from any project the staff token can see, or clear it to fall back to slug-match.

    • New picker on the Client details form — a lazy-loaded dropdown listing every Vercel project visible to the connected staff token, with a filter input, project metadata (framework + production branch), a "matches slug" hint when the org slug equals a project name, and a Clear button to revert to auto-detect.
    • Two new columns on `portal_organizations` (runtime DDL): vercel_project_id (canonical, survives renames) and vercel_project_name (display label so cards don't have to round-trip Vercel just to render "Connected to <name>"). The picker writes both; the resolver prefers id.
    • Single resolver in `lib/portal/integrations/vercel-resolver.ts` — one function, resolveVercelProjectForOrg, used by DeploymentsCard, UsageCards, the manual changelog sync, and the hourly cron. Resolution order: pinned id → pinned name → slug-match. Falls through tiers if a tier returns nothing (e.g. stale id after a project deletion still tries name + slug).
    • Empty states get a CTA when project lookup fails for a client: instead of just "no project found", the card shows "Open client details" linking to the Vercel project picker so the staffer can fix it in one click.
    • `GET /api/admin/vercel/projects` — small new endpoint backing the picker. Returns each project's id, name, framework, production branch, and latest deploy URL.

    Solagon Team

    solagon.com

  15. Preview form: stop browser-level validation from silently blocking Save changes

    The preview form had `<input type="url">` on the target URL field and a `pattern` regex on the slug field. When the staffer typed something the browser thought was malformed (e.g. `bluefin.com` without a scheme), the browser blocked the form submit at the OS level — no toast, no error, no navigation, just nothing. Relaxed the inputs to plain text and normalize on save instead.

    • Target URL input dropped from `type="url"` to `type="text"` with server-side normalization that prepends https:// when the staffer leaves the scheme out.
    • Slug input dropped its `pattern="[a-z0-9-]+"` attribute — the onChange already normalizes per-keystroke and the save handler re-normalizes pre-submit, so the browser-level validator was redundant + actively hostile.
    • Save handler normalizes everything pre-submit: trims, prepends scheme, lowercases + dash-collapses the slug, and throws a friendly error if either is empty after normalization. The toast now surfaces those.
    • PATCH and POST routes wrap the DB write in try/catch and translate Postgres' raw duplicate key value violates unique constraint into That slug is already in use by another preview. Pick a different one. so the toast tells the operator exactly what went wrong.
    • Dropped the `startTransition` around the post-save navigation — wrapping router.push() in a transition deferred the navigation visibly enough that operators saw the form sit there for a beat before moving, which compounded the "did anything happen?" feeling.

    Solagon Team

    solagon.com

  16. Editing a client preview now redirects back to the client's preview tab

    Saving a preview edit was firing the toast but staying on the same form, so it felt like nothing happened. And the only way out (breadcrumb / sidebar) landed on the team-wide /admin/preview list instead of the client's preview tab the staffer started from. The edit page now resolves the preview's org server-side and threads the right redirect into the form, so saving a Bluefin preview lands you back on /admin/clients/bluefin/preview.

    • Edit save navigates to the client's preview tab when the row belongs to a client org (/admin/clients/<slug>/preview), or to /admin/preview for team-scoped previews. Previously it fired router.refresh() and stayed on the edit screen — toast appeared but no movement, easy to miss.
    • `/admin/preview/[id]` resolves the org server-side and threads successRedirect into PreviewForm. Breadcrumbs match too: editing a Bluefin preview now reads Admin → Bluefin → Preview → <name> instead of Admin → Preview → <name>.
    • PreviewForm's edit-mode `successRedirect` prop is the single switch — pass it from any caller that wants to control where Save changes lands the staffer.

    Solagon Team

    solagon.com

  17. Preview saves now confirm + auto-attach to client; sticky workspace stops snapping back to Solagon

    Two related papercuts: adding a preview from inside a client workspace silently created an org-less Solagon-team preview (so it never showed up in the client's preview list), and saving on either the create or edit form rendered no toast — clicks felt like they'd been swallowed. Plus: the workspace switcher kept snapping back to Solagon on hard refreshes of any top-level admin URL even though the staffer was deep in a client's surface.

    • Preview create now attaches to the right org. The shared PreviewForm accepts an organizationId prop (and a successRedirect) — when the staffer hits New preview from a client's preview tab, the URL forwards ?org=<slug>, the new-preview page resolves the org server-side, and the form posts the right id so the row lands in the client's preview list (not the team-scoped All previews list, which is where it was silently going before).
    • Every preview save fires a toast. Adds, edits, and deletes all surface a toast.success(...) (or toast.error(...) with the API's error message) so the staffer never has to wonder whether the click did anything. Edit-mode also keeps the staffer on the same screen — no surprise navigation — and just refreshes the server data after save.
    • Per-client preview tab gets a New preview CTA in the page header (only shown once at least one preview exists; the empty-state still uses the friendlier PreviewOnboarding flow). The CTA links to /admin/preview/new?org=<slug> so the new-preview page can attach the org automatically.
    • Preview API auth fix: PATCH and DELETE on /api/admin/preview/[id] previously required isSuperadmin, while POST allowed any Clerk staff with an org membership. Non-superadmin staff could create previews but their saves silently 401'd — that's why "Save changes" looked like it did nothing. The two endpoints now share the same staff-friendly auth check.
    • Sticky workspace cookie now SSR's correctly. The dashboard layout reads the solagon_admin_ws cookie server-side via cookies() and threads the resolved slug as initialActiveSlug into AdminShell. The SSR'd sidebar already shows the picked client workspace — no flash of "Solagon" before the client cookie-read kicks in, no snap-back when navigating into a top-level admin URL like /admin/preview/<id> that doesn't carry the slug in its path. The client-side cookie reader stays as a fallback for in-session writes that haven't round-tripped to the server yet.

    Solagon Team

    solagon.com

  18. Marketing pages render as static HTML again — homepage opens instantly from a new tab

    solagon.com was waiting on a full Lambda render every visit, even though the homepage was supposed to be cached. Found the root layout was calling `headers()` and `cookies()`, which silently opted the entire app out of static rendering. Removed both calls and the homepage + every marketing page now build as static HTML at the edge.

    • Root cause: the root layout (app/layout.tsx) called headers() to detect subdomain hosts and getCurrentLocale() (which itself calls cookies() + headers()) to set the <html lang>. Both are dynamic-only Next.js APIs — calling them anywhere in the layout opted *every page in the app*, including the homepage with its revalidate = 300 ISR directive, into per-request server rendering. Every new-tab visit was paying for the middleware + layout + 13 admin-db data fetchers + render before the first byte left the server.
    • Fix: dropped the headers() call entirely (the inline data-host-mode pre-paint script + the .marketing-chrome-* CSS rules in globals.css already hide marketing chrome on subdomain hosts client-side). Pinned the layout's locale to defaultLocale for SSR — the middleware still detects Accept-Language and sets the locale cookie, and per-route metadata functions can still call getCurrentLocale() if they need it without dragging the rest of the site into dynamic rendering. Always render SiteThemeScript and SiteChrome; the script self-bails on subdomain hostnames already.
    • Build output went from `ƒ` (dynamic) to `○` (static) on every marketing page: /, /home, /case-studies, /case-studies/<slug> (SSG), /company, /contact, /industries, /industries/<slug>, /insights, /insights/<slug> (SSG), /locations, /locations/<city> (SSG), /playbooks, /products, /privacy, /terms, /site-check, /careers, /solutions/<slug>, /support, /support/articles. New-tab visits to solagon.com now serve cached HTML from the edge — no Lambda spin-up, no DB roundtrips, no waiting on the data-fetcher fan-out.

    Solagon Team

    solagon.com

  19. Live analytics data, real client submissions, working changelog, favicon-as-logo

    Four big admin upgrades landed in one pass: the Analytics tab now pulls live numbers from Google Search Console + Ahrefs (was waiting on a "next pass"), the per-client Submissions tab is fully wired to /api/contact via the new clientSlug routing, the per-client Changelog stopped being "coming soon" and is now fully editable + auto-imports Vercel deploys + incidents, and every client now has a websiteUrl that drives an auto-favicon-as-logo throughout the dashboard.

    • Analytics tab pulls live data. New GSC fetcher (lib/portal/integrations/gsc/pull.ts) signs a JWT-bearer assertion with the connected service-account JSON, mints an access token via oauth2.googleapis.com, and pulls a 28-day rolling-window summary: total impressions / clicks / CTR / avg position plus the top 10 queries and top 10 landing pages. New Ahrefs fetcher (lib/portal/integrations/ahrefs/pull.ts) hits /site-explorer/domain-rating, /site-explorer/metrics, and /site-explorer/organic-keywords for DR, organic monthly traffic, total keywords, referring domains, and the top 10 ranking keywords with positions. Both render directly on /admin/insights/analytics — each integration streams in its own <Suspense> so a slow fetch doesn't block the rest.
    • Per-client Submissions tab is fully functional. Added organization_id to portal_contact_submissions via runtime DDL (no migration). /api/contact now accepts an optional clientSlug field — when present we look up the org and tag the submission, so the per-client Submissions queue at /admin/clients/<slug>/submissions filters on it. The page surfaces the real submissions list with status counts, plus a setup card with a copy-pasteable POST snippet so client devs know exactly how to wire their form. Resend handles the email side using the same RESEND_API_KEY + CONTACT_FROM_EMAIL + CONTACT_TO_EMAIL env vars that already power the team form.
    • Per-client Changelog stopped being 'coming soon'. New portal_client_changelog table (also runtime DDL) with kinds: ship / incident / note / milestone. Page at /admin/clients/<slug>/changelog shows a KPI strip (total / ships / incidents / milestones), a full timeline with kind-coded icons + tones, and an inline composer for new entries — kind, title, markdown body, when-it-happened. Each entry can be hidden / published / deleted in place. New cron at /api/portal/cron/sync-client-changelog (runs hourly via vercel.json) auto-imports successful production deploys per client (matched on org slug → Vercel project) and recent open + resolved incidents — idempotent on (external_kind, external_id) so it's safe to run repeatedly.
    • Website URL + auto favicon-as-logo. Added website_url to portal_organizations via runtime DDL. The new-client form now requires the website URL during onboarding; the Client Details edit page exposes it for editing. New lib/portal/favicon.ts resolves a logo URL from any website (google.com/s2/favicons proxy) — used as the org logo unless an explicit logoUrl override is set. The workspace switcher and the /admin/portal/clients table both render the favicon-derived logo so client workspaces are recognizable at a glance instead of two-letter initials.
    • Schema additions wired through Drizzle: organizations.websiteUrl, contactSubmissions.organizationId, and the brand-new clientChangelog table are all typed in lib/portal/schema.ts. The runtime DDL bootstrap in lib/portal/client-features-schema.ts keeps a fresh DB compatible without forcing a migration step.
    • API endpoints added: POST /api/admin/clients/<slug>/changelog (create entry), PATCH/DELETE /api/admin/clients/<slug>/changelog/<id> (update / delete). Same staff-only auth as the rest of /api/admin.

    Solagon Team

    solagon.com

  20. Client workspaces get the same Insights surface as the team

    The Solagon-team Insights pages (Logs, Usage, Speed, Uptime, Preview) shipped polish + deeper data over the last few days; the matching client-scoped pages were still on their old "coming soon" cards. This pass brings every client workspace to feature parity — same KPI strips, same charts, same peek panels, same persistence patterns.

    • Client Uptime is fully rebuilt: KPI strip, response-time area chart, per-site uptime list with progress bars, AutoRefresh polling (60s), CronFreshnessBanner, EntityPeek wrapped on every site row. Identical to /admin/insights/uptime, just filtered to that client's monitored sites.
    • Client Logs ports the team-side DeploymentsCard straight in. Refactored that component to accept projectName + ownerLabel props with 'solagon' as the default, so the same code powers both surfaces. Each client's Vercel project is matched by their org slug — clients whose Vercel project is named the same as their slug get the full deploy surface (success rate, deploy cadence area chart, build-duration trend, branches shipped, deploy-author leaderboard, recent deployments with EntityPeek). Clients whose project name doesn't match render a clear empty state explaining how to fix it.
    • Client Usage ports UsageCards with hidePlatformFootprint — replaces the Postgres + Blob KPIs with deploy/success metrics that actually reflect the client's project, and skips the platform-footprint card grid (those numbers belong to the shared Solagon platform, not the client). Domains, deploy cadence, project metadata still render with the same polish as the team page.
    • Client Speed insights now uses the persistent portal_speed_audits storage layer (the same getLatestSpeedAudit / listRecentSpeedAudits helpers the team page uses). Default URL is the client's first monitored site. The full surface is here: comparison rings, mobile + desktop audit cards, 30-audit performance trend, "What we audit" + "What the targets mean" reference cards. Clients with no monitored sites yet get a clear empty state pointing to /admin/status/sites/new.
    • Client Preview matches the team Preview surface — KPI strip (total previews, active share, total views, last view), status mix card, feature explainer, and a recent-activity feed ranked by last-viewed. All filtered to the client's organizationId.
    • Client Analytics got chip-driven range selection (?range=7|14|28|90), a copyable org slug IdBadge in the chart header, and a tighter chart-card treatment.
    • Refactors: DeploymentsCard and UsageCards are now both parameterized by project name + owner label; the client wrappers are tiny pass-throughs. New ClientDeploymentsCard.tsx is just a 30-line wrapper that composes the existing DeploymentsCard and skeleton.

    Solagon Team

    solagon.com

  21. Linear + Clerk interaction patterns blended into the admin

    The admin dashboard already had Vercel-style polish; this pass layers in the Linear-style fast interactions (peek panels, inline edits, keyboard shortcuts, optimistic toasts) and Clerk-style developer ergonomics (one-click ID copy, masked secrets, setup checklists) on top of what's there. No re-skin — just a meaningfully faster admin to use.

    • Inline-edit on tables. The booking-staff table swapped its modal-style edit form for click-to-edit cells: tap a name, role, or status pill to edit in place, hit Enter to save, Esc to cancel. Status changes flip optimistically and revert if the PATCH fails. Tab in a text edit commits the change before focus advances, so editing two fields in a row feels atomic the way it does in Linear.
    • Hover/click peek panels. Clicking (or hovering for 250ms) any client, deployment, or uptime-site link slides a 440px right-side panel in with a quick summary — branding/avatar, member count, monitored sites and reports for clients; build duration, branch, sha, commit message, build state for deploys; 24h uptime + open incidents for sites. ⌘+click and middle-click bypass the panel and use the underlying link, so power-users aren't trapped. Esc and click-outside close the panel.
    • Keyboard shortcuts. Press ? anywhere in the dashboard for a cheat-sheet of every binding. g h jumps to overview, g a to articles, g c to clients, g s to submissions, g u to uptime, g l to logs, g p to preview, g d to docs. c a creates an article, c c creates a client, c i creates an incident. / focuses the command palette. Bindings auto-pause while you're typing in any input, textarea, or contenteditable.
    • Toast notifications. Replaced every alert(...) and inline error pill with a real toast system mounted at the shell. toast.success, toast.error, toast.warn, and toast.info accept a title + optional body + optional inline action. Errors stay visible 6s by default; success toasts auto-dismiss in 3.5s. The booking-staff add-flow uses these to confirm reactivations, role saves, and status flips.
    • Filter chips + table search. The clients table got a Linear/Clerk-style chip row (All / Active / Inactive with live counts) plus an inline search box that filters on name + slug. State lives in URL params (?filter=active&q=acme) so refreshes and shared links survive — no parent state needed.
    • Copy-to-clipboard everywhere. New IdBadge renders any short string (slug, UUID, sha, email) as a monospace pill with a 1-click copy button. Used on the staff table for calendar emails and the clients table for slugs. The companion SecretReveal component masks tokens (•••••••• abc1) with a reveal toggle and copy button — ready for any future token / webhook display.
    • Setup checklists — Clerk's signature onboarding pattern. New <SetupChecklist> renders a compact "next steps" card with done / required / pending states + per-row jump-in buttons. Drop it into any landing page that needs guided setup (Integrations, Booking, Status, etc.) without rebuilding the page.
    • New API routes for the peek panels: /api/admin/peek/client/[id], /api/admin/peek/deploy/[id], /api/admin/peek/site/[id]. Each returns a tiny payload tuned for the panel and is private, max-age=15-30s cached so re-opens are instant.
    • Component primitives shipped this pass: Toaster + toast, CopyButton, IdBadge, SecretReveal, EditableCell, FilterChips, SetupChecklist, KeyboardRouter, KeyboardHelp, PeekHost + PeekProvider + EntityPeek. All exported from @/components/dashboard.

    Solagon Team

    solagon.com

  22. Logs / Usage / Storage / Preview — deeper data + polished charts

    Four admin pages got a real polish pass with much more in-depth data — Logs and Usage drop the chunky bar chart in favor of a Vercel-style area chart with a per-bucket tooltip, Usage and Storage now read live from Neon Postgres + Vercel Blob, Preview filled out with rollups + a recent-activity feed, and the docs theme switcher actually flips light vs dark.

    • Logs page: new 4-up KPI strip up top (success rate, deploys/14d, median build, last deploy) plus a smooth area chart for daily deploy volume with a custom tooltip that splits each bucket into READY / in-flight / failed. Added a build-duration trend chart (one point per build), a branch-shipped breakdown, and a deploy-author leaderboard with per-author success rate. Each row in the recent-deployments list now shows the build duration inline.
    • Usage page rebuilt: KPI strip surfaces Postgres bytes on disk, Blob bytes, build minutes, and latest deploy state. New Neon Postgres card lists the top user tables ranked by size with row counts and a tone-colored bar; new Vercel Blob card lists the top folders ranked by bytes with object counts. Domains card moved into a 2-column grid so it stops dominating the page.
    • Storage page: replaces the old "runtime + counts" dashboard. KPI strip (DB size, user table count, Blob bytes, Blob objects) sits above a side-by-side Postgres tables breakdown and Blob folder breakdown — both with bars proportional to size — plus a "Recent blob uploads" list with direct links to each file. Existing bundled-snapshot vs. resolved comparison kept below.
    • Preview page filled out: 4-up KPI strip (total previews, active share, total views, last view), a status-mix card (active / paused / archived / gated breakdown), a feature card describing password protection, view tracking, branded chrome, and custom expirations, and a recent-activity list ranked by last-viewed across both client + team previews. Plus a "How this surface works" explainer covering URL shape, view counting, password gating, and scope switching.
    • Platform-stats helper module: new lib/portal/integrations/platform-stats.ts shares the Neon (pg_database_size, pg_total_relation_size, pg_stat_user_tables) and Vercel Blob walks between Usage and Storage so the same numbers render consistently on both pages without duplicate queries.
    • Admin docs theme switcher fixed: the toggle button used dead Tailwind classes (text-secondary-ink, hover:bg-canvas-2, …) that produced no styles, so light / dark looked identical. Switched to inline CSS-var styles, flipped the icon convention so it shows the CURRENT theme (Sun = light, Moon = dark), and added a defensive [data-docs-theme='light'] rule in globals.css so the docs root re-asserts light tokens even when the marketing site has stamped data-site-theme="dark" on <html> upstream.
    • Dark-mode text contrast pass on the admin dashboard. The tertiary + disabled text tokens were #71717A and #52525B — only ~3.9:1 and ~2.0:1 against the deep-dark dashboard surface, well below WCAG AA for body copy. Caption / hint / "by @author" text was effectively unreadable on the overview, articles, and most insights pages. Bumped tertiary to #8E8E96 (now ~5.9:1, clears AA) and disabled to #6E6E76 (now ~3.8:1, clears AA-large). Single-token change so it propagates across every page that reads dash.textTertiary / dash.textDisabled — no per-component edits.
    • Booking staff add upserts by email. Adding a booking-staff member with an email that already exists in portal_booking_staff (e.g. a row that an FK from past meetings blocked from being hard-deleted) used to fail with duplicate key value violates unique constraint "portal_booking_staff_email_idx". The POST endpoint now upserts: if the email already has a row, it&apos;s reactivated and refreshed with the new name + role, with a revived: true flag in the response so the staff table can alert you that an existing row was reused.
    • Preview page no longer crashes on a string `lastViewedAt`. The neon-http drizzle driver hands raw timestamps back as ISO strings even when the column is typed Date, so relative(d.getTime()) blew up the Preview tab when any preview had been viewed. The shared relative() helper and KpiStrip now coerce strings → Date defensively before formatting.

    Solagon Team

    solagon.com

  23. Insights chart polish + Usage rebuild + Uptime backfill

    Every chart in the admin Insights surface got a real polish pass — smooth curves, gradient fills, proper headers, alignment fixed. The Usage tab is fully rebuilt with domains, env vars, deploy cadence, and rollups. Uptime now has a one-click backfill so the chart isn’t empty after a fresh deploy.

    • Uptime chart: bumped from 224px → 288px tall, response-time area now uses curveType="monotone" + gradient fill, axis labels show only at the start/end of the range to keep the rail clean. The right-side bar list got replaced with a real per-site uptime row component — site name + tabular percentage + a tone-colored progress bar (green ≥99.9%, amber 95-99.9%, red <95%) + a check-tally caption. Header surfaces avg / peak response and total check count as badges.
    • Submissions trend (Analytics): 14-day area chart with gradient + smooth curves, integer ticks, friendly empty state with icon when nothing’s submitted. Height bumped to 240px so it actually reads.
    • Deploys trend (Logs): now a *stacked* bar chart with READY (emerald) and other (amber) stacked together — at-a-glance "did anything fail" instead of two confusingly overlapping series. Empty state with rocket icon when there’s no recent activity.
    • New Speed insights summary chart. Mobile + desktop performance scores side-by-side with conic-gradient progress rings (green/amber/red banded) + a per-metric BarList showing how each Core Web Vital scores against its target. The two PSI promises are kicked off once at the page level and shared between the comparison chart and the detailed cards via a new auditPromise prop on SpeedAuditCard — so we don’t double-fetch even though three components read the data.
    • Usage page rebuilt from scratch. Hero row of four metric tiles (latest deploy state, production branch, database, env-var count). Long Vercel hostnames now truncate + break-all so they stop blowing out of the cards. New "Deploy cadence · 14 days" stacked bar (READY / building / failed) with a sidecar rollup card for the 14-day totals. Live "Domains attached" list pulled from /v9/projects/<id>/domains with verified-vs-pending badges and apex/branch/redirect annotations. Clear "what's measurable today" footer that explains why bandwidth isn't there (Vercel observability scope) so it's not a mystery.
    • New Vercel API helpers: listVercelProjectDomains and countVercelProjectEnv (/v9/projects/.../domains and /v10/projects/.../env). Each one fan-outs in parallel with the existing project + deployments calls so the whole page still streams under one Suspense boundary.
    • Uptime backfill: new POST /api/admin/uptime/backfill endpoint synthesizes 288 "up" checks (24h × 5min cadence) for every active site that has fewer than 10 recent checks. Response times sample around the site's last recorded value with ±25% jitter. Each row is tagged errorCode = 'backfilled' so a DELETE on the same endpoint cleans them out when the real cron data catches up. Wired to a "Backfill 24h" button in the cron-freshness banner — fixes the "chart is empty after fresh deploy" problem without waiting for hours of cron ticks.

    Solagon Team

    solagon.com

  24. Client controls + data freshness pass

    Six fixes that came out of test-driving a real client onboarding: invite emails are now opt-in, SEO bundles automatically with Website, Site Details became a real Client Details editor, save no longer yanks you out of the client workspace, the uptime tab actually reflects what the cron is writing, and the Speed insights page no longer aborts on slow pages.

    • Invite emails are opt-in. Adding a client-side user no longer fires a Clerk invitation email by default. The add row has a "Send login email now" checkbox (off by default), and every existing client row gets a "Send login email" button you can press whenever you’re ready to give them access. New endpoint: POST /api/portal/admin/organizations/<id>/memberships/<membershipId>/send-invite.
    • SEO is bundled with Website. Removed SEO from the new-client services picker. When an org is created, both website and seo are auto-attached server-side. Existing orgs that already had SEO attached are untouched.
    • Site Details → Client Details. New page at /admin/clients/<slug>/details with a real editable form for company name, slug, logo URL, brand color, primary contact, Stripe customer id, internal notes, and the active/inactive toggle. Sidebar label and links updated; /site redirects to /details for old bookmarks.
    • Save no longer bounces you out of the client workspace. Editing a client through the legacy ClientForm used to router.push you to /admin/portal/clients/<id> after save. Now it just refreshes server data and stays put. Same fix for Client Details — slug rename uses router.replace to keep you in the same workspace path.
    • Uptime updates show up in the dashboard. Next.js 14 keeps dynamic-page renders in the client-side Router Cache for 30s by default — that was masking fresh check rows the cron was writing. Set experimental.staleTimes.dynamic = 0 in next.config so admin navigations always re-fetch.
    • Speed insights no longer fails with "operation aborted". Bumped the PSI fetch timeout from 25s → 80s (heavy pages routinely take 30–60s for a real Lighthouse run) and added maxDuration = 90 on the Speed route so Vercel doesn’t kill the request first.
    • Manual Lighthouse runs: the Speed insights page got a controls bar — pick any URL, hit "Run audit" or "Re-run current". Drives the page via search params, so the Suspense boundary remounts and refetches both mobile + desktop.

    Solagon Team

    solagon.com

  25. Charts, uptime visualization, real preview thumbnails, faster navigation

    Big quality pass on the admin Insights surface: live charts on every page, a 24h response-time graph on Uptime, real screenshot thumbnails on Preview cards, and Suspense-streamed data so the dashboard now paints instantly between pages.

    • Preview thumbnails actually render now. The old approach iframed the target site and scaled it with transform: scale. That paints blank or half-loaded on every site that sets X-Frame-Options: DENY or a CSP frame-ancestors policy — i.e. most production sites. New /api/admin/preview/thumb proxies through Microlink, returns a real PNG, and caches it for 24h on the edge + 1h in-browser. The Vercel-style preview cards now show the actual rendered site.
    • Uptime cron diagnostics on every uptime tab. A banner at the top tells you when the most recent check landed and turns red if the cron hasn’t ticked in >10 minutes. There’s also a "Run checks now" button that probes every active site sequentially through the new /api/admin/uptime/run-all endpoint — useful when the cron is paused on a Hobby plan or you want to bootstrap a fresh deploy.
    • 24-hour response time chart on Uptime. Tremor AreaChart plotting average response time per hour across every Solagon-owned site, plus a per-site uptime-% bar list to the right. Built on the existing site_checks history — no new data ingestion needed.
    • Speed insights now streams. Each Lighthouse run takes 10–20s. Wrapped both audits (mobile + desktop) in <Suspense> with proper skeleton cards, so the page shell paints in milliseconds and the audits stream in independently. No more 25-second navigation freezes.
    • Logs page got the same Suspense-streaming treatment plus a 14-day deploy cadence chart (Tremor BarChart showing READY vs total deployments per day) sitting above the deployment list.
    • Usage page now Suspense-streams the Vercel project lookup; the snapshot tiles render under skeletons until the live data lands.
    • Analytics page got a 14-day submissions trend chart (AreaChart), so the team-level analytics tab now opens with at least one real graph even before GSC is connected.
    • Performance: every heavy remote integration call (PageSpeed Insights, Vercel REST, Microlink) is now off the critical render path. Navigation between admin pages feels instant — the page chrome paints, then data streams in. No more waiting on a 25s API call before the first byte.

    Solagon Team

    solagon.com

  26. New-client form fixed; team & access goes live

    The new-client form picker now plays nice in dark mode and drops three legacy services. The team & access page on every client workspace can now actually add, change roles for, and remove both Solagon staff and client-side users — no more "run a CLI command" hint.

    • Dark-mode fix on the new-client form. ClientForm rebuilt against dashboard tokens — every input, the slug field, the accent picker, the service-card grid all switch cleanly between dark and light. The "selected service" state no longer flashes white on dark.
    • Service picker trimmed. Paid Ads, Paid Social, and LinkedIn Ads are gone from the picker. Existing clients that already had them attached are untouched (we filter at the form layer, not the DB).
    • Team & access on the client workspace is fully interactive. New TeamAccessManager component on /admin/clients/<slug>/team lets you add staff or invite client users, change roles inline (Staff↔Admin, Owner↔Member), and remove memberships with confirmation.
    • Adding a Solagon staffer skips the email dance — we look the user up in Clerk by email and link them immediately if they exist, dropping a pending:email placeholder otherwise. Adding a client user still sends the Clerk invitation email so they finish signup themselves.
    • New REST surface: POST /api/portal/admin/organizations/<id>/memberships for invites, PATCH/DELETE /.../memberships/<membershipId> for role changes + removals. Auth accepts JWT or Clerk staff/superadmin (the old endpoint was JWT-only).
    • Workspace setup checklist on every client overview. When you spin up a new client, the workspace shows a 6-step checklist (org / services / monitored site / preview / data sources / report) with one-click CTAs to each missing piece. The metric cards now say "No data — connect a data source" when empty instead of just showing zeros.

    Solagon Team

    solagon.com

  27. Admin migration: every page now on the design system

    Closed out the last seven hold-outs — uptime’s recent-checks table, incident manage + new, preview create/edit, support ticket detail, booking meeting types, and monitored-site create/edit. Hex colors, `rounded-xl` pills, and orange focus rings are gone from the dashboard.

    • Status: RecentChecks, IncidentManage, IncidentNewForm, and SiteForm rebuilt against Card/Button/StatusBadge + dash tokens. Severity and status pickers are now token-driven (no more #A24A05 hard-coded).
    • Booking: MeetingTypeForm (264 lines) migrated. JSON active-hours editor now uses the standard mono dash-input pattern; the active toggle styles match the rest of the dashboard.
    • Preview: PreviewForm (235 lines) migrated. Status picker (active / paused / archived), password gate block, and delete affordance all now render in dark + light cleanly.
    • Support: TicketDetail (244 lines) migrated. Status pill is now a StatusBadge driven by tone (open=warn, in_progress=info, etc.); internal notes thread uses Card + surfaceMuted.
    • Repo-wide grep for #ff8b11 / rounded-xl / text-[# inside app/admin/(dashboard) returns zero hits — every page and form is on the design system.

    Solagon Team

    solagon.com

  28. Insights pages start showing real data

    Speed insights, Logs, Usage, and Analytics on the team workspace are no longer placeholders — they now pull live from PageSpeed Insights, the Vercel REST API, and our own monitored-sites Postgres.

    • Speed insights: every page load runs Lighthouse against solagon.com for both mobile and desktop via Google’s PageSpeed Insights v5 API. Cards show the Performance score (color-coded), lab metrics (LCP, INP, CLS, FCP, TBT, TTFB) with target thresholds, and CrUX 28-day field data when Google has it.
    • Logs: lists the 25 most recent Vercel deployments for the solagon project — branch, commit SHA, commit message, deploy state with color-coded status pill, deploy author, age, and a "Logs" link that opens the full build logs on vercel.com.
    • Usage: snapshot cards for production branch + framework, latest deployment state, and Neon database connection. Hits the Vercel projects API live with your personal token. Reads NEON_PROJECT_ID from env to confirm the database side.
    • Analytics: replaced the cold empty state with real signals — sites monitored / up, submissions in the last 7 days, total preview views, a per-site health table, and a recent-leads feed with a one-click jump into each submission. A Search Console card prompts to wire GSC for impressions + queries when ready.
    • Per-staff Vercel token wiring. The Logs and Usage pages call getVercelToken() which prefers the per-user encrypted token from staff_integrations and falls back to env-configured tokens — so each staffer’s view reflects what *their* token can see.
    • Soft-failure everywhere: every fetch wraps in a timeout + try/catch and returns a structured { ok: false, error } to the page. A Vercel 401 doesn’t blank the page; it renders a focused error card so you know exactly what to fix.

    Solagon Team

    solagon.com

  29. Integrations: live verification + step-by-step setup

    Every integration you connect on the admin Integrations page now does a live API ping to confirm the token actually works, and each provider has its own collapsible "How to set up" walkthrough — so you don’t have to leave the dashboard to figure out which scopes to ask for.

    • Solagon team Preview tab is now Vercel-style. /admin/preview (the Insights → Preview entry while you’re in the Solagon workspace) used to be a flat list of every preview project. It now mirrors the per-client preview view: a thumbnail card for the Solagon demo (anchored to solagon.dev/demo), or a one-submit onboarding flow if it hasn’t been set up yet. Switching to a client workspace still gets you that client’s scoped preview the same way.
    • Reusable `PreviewCard` + `PreviewOnboarding` components. Both surfaces (team + client) now share the same card and onboarding flow — same iframe-thumbnail trick, same status pill, same view-count + last-viewed footer — so they stay in sync as we polish.
    • The flat "all previews" list moved to `/admin/preview/all` with a small "All previews →" link on the new team preview page, so you can still drop into the table view when you need to manage everything at once.
    • Live verification on connect. Saving a Vercel / Cloudflare / GitHub / Ahrefs / OpenAI / Anthropic key now hits the provider’s API server-side before the row is finalized — Vercel’s /v2/user, Cloudflare’s /user/tokens/verify, GitHub’s /user, Ahrefs’s subscription-info, and the model-list endpoints for OpenAI + Anthropic. The result is persisted on the row and surfaced inline.
    • Re-verify button on every connected card. Click the badge to retest the stored token without re-entering it — useful when a token gets revoked, scopes change, or you want to confirm before running a long workflow.
    • Status badges reflect reality. Cards show Connected / Error based on the live result, with a one-line summary ("Authenticated as @stonebaldwin", "Workspace unit limit: 5,000,000", etc.) and a timestamp of the last check.
    • Step-by-step setup instructions for every provider. Each card has a collapsible "How to set up [Provider]" section with numbered steps: where to go, which token type to pick, which scopes to enable, what to copy/paste, and what to expect from the verification step. Recommended scope lists are spelled out so you don’t over-grant by accident.
    • Per-user privacy is enforced at the database layer. Each integration row is keyed to the staffer’s Clerk user id with a UNIQUE (clerk_user_id, provider) constraint — other staffers literally cannot read your tokens. AES-256-GCM encryption at rest for the secret itself, never echoed back across the wire.
    • Google Search Console: shape-validates the service-account JSON (or refresh-token format) before saving, so you find out about a malformed paste in milliseconds instead of when a downstream report tries to call the API.

    Solagon Team

    solagon.com

  30. Admin dashboard refresh

    A new design system, dark + light themes, a refined Cmd+K palette, sidebar submenus, internal docs, a redesigned user menu, and a polished changelog page.

    components/dashboard/AppShell.tsxtsx
    // One shell, both dashboards. Ctrl+K everywhere, theme in the
    // user menu, collapsible sidebar with sticky preference.
    
    <AppShell
      sidebar={<Sidebar sections={SECTIONS} footer={<UserMenu />} />}
      commandItems={[...navItems, ...quickActions, ...systemActions]}
      collapseStorageKey="solagon-admin-sidebar-collapsed"
      themeStorageKey="solagon-admin-theme"
    >
      {children}
    </AppShell>
    AppShell hosts the Cmd+K palette, theme switcher, sidebar collapse, and the user account chip.
    • Settings sidebar group retired. Users + Integrations no longer live in the main sidebar — they’re reachable from the user-account popover (sidebar bottom → User settings) and the right-hand submenu picks up /admin/users automatically. One fewer category in the day-to-day sidebar.
    • Notifications scope to your accessible orgs. Superadmins still see everything; regular staff only get notifications for tickets and reports for the clients they belong to. Contact submissions stay global since they’re top-of-funnel prospects. The popover items are clickable and link straight to the relevant submission, ticket, or report.
    • Case studies and testimonials editors migrated to the new design system. Same fields and behavior, but they now match the rest of the admin — themed inputs, tabbed sections for case studies, dashboard tokens throughout, dark + light parity.
    • Integrations sidebar entry — added under the Settings group at /admin (the user-settings submenu still has it too). Clicking it goes straight to /admin/user-settings/integrations.
    • Project-wide env credentials now surface as connections. The integrations page detects which providers already have an env-configured key (ANTHROPIC_API_KEY, VERCEL_OIDC_TOKEN, GOOGLE_PAGESPEED_API_KEY, etc.) and shows a "Project-wide" badge on those cards. Connecting a personal token still overrides the env fallback for that staffer.
    • Support tickets now route to the staffer who owns the client. When a portal-authenticated user submits a ticket, we resolve their org via portal_memberships, save it on support_tickets.organizationId, and email the alert to every staffer assigned to that org instead of the generic support@solagon.com inbox. Anonymous tickets still hit the fallback address.
    • Client-scoped Preview tab at /admin/clients/<slug>/preview. Vercel-style: if a preview project exists for the client, you get a card with a live iframe thumbnail of the deployment, status pill, view count, last-viewed timestamp, and Open / Edit buttons. If no preview is set up yet, you walk through a two-step onboarding (intent + form) that creates the preview_projects row tagged to this org with one submit.
    • Preview API now accepts staff-with-membership (was JWT/superadmin-only) so the per-client onboarding flow works for non-superadmins.
    • Admin sidebar restructured. Dropped the empty Workspace group, dropped the redundant Status & uptime (it lives under Insights now), renamed Site insights to just Insights, moved Storage from Settings and Preview from Operations into the Insights group. Sidebar now reads: Overview · Content · Operations · Insights · Client portal · Settings.
    • Overview page rebuilt into a Solagon-wide dashboard. Top of the page shows live metrics (sites up, new submissions, open tickets, upcoming meetings); below it, an uptime widget, recent submissions + open tickets feeds, drill-in tiles for every Insights tab, content rollups for articles/case studies/testimonials, an open-incident callout when something’s down, and the upcoming meetings list.
    • Team-level Insights tabs now show solagon.com data by default. Analytics, Uptime, Speed insights, Logs, and Usage all surface whatever Solagon-owned data we have for our own site, with a "Switch to a client" banner at the top of every tab so jumping into a client workspace is one click. The old "pick a client" picker is gone — the workspace switcher in the sidebar already covers that.
    • Per-staff integrations at /admin/user-settings/integrations. Each Solagon staffer wires up their own Vercel, Google Search Console, Cloudflare, Ahrefs, GitHub, OpenAI, and Anthropic credentials. Tokens are encrypted at rest with AES-256-GCM and kept private to the staffer who connected them — they’ll feed the client-scoped logs / analytics / speed / usage tabs.
    • New portal_staff_integrations table is created on first use (no migration needed). Connections are upserted via /api/admin/integrations/<provider> with POST (connect / update) and DELETE (disconnect). The encrypted secret never leaves the server.
    • Booking staff are now fully editable at /admin/booking/staff — inline edit form lets you change a staffer’s name, calendar email, or role; toggle active/inactive; remove with confirmation. Migrated to the new design system in the process.
    • Article editor (InsightForm) migrated to the new design system — dashboard tokens, themed inputs, dark + light mode friendly.
    • Bug fix: routing collision between the legacy /admin/insights/[id] redirects and the new /admin/insights/[tab] site-insights pickers — old redirect routes deleted (the canonical articles surface is /admin/articles).
    • New Site insights group in the admin sidebar — Analytics, Uptime, Speed insights, Logs, Usage. From the team workspace, clicking a tab opens a client picker; from inside a client workspace, it goes straight to that client’s data. Same routes either way (/admin/clients/<slug>/<tab>).
    • Adding a new client now auto-wires the integrations: the website service is attached, and if you set a Site URL, a monitored uptime site + a preview project at solagon.dev/<slug> are created automatically. The new client immediately shows up in your workspace switcher.
    • Internal admin docs at /admin/docs rebuilt to match the public docs subdomain — same three-column shell, collapsible sidebar with chevron toggles, light/dark theme via the same data-docs-theme attribute. Removed from the dashboard sidebar; reachable via the Docs link in the user-account popover at the bottom of the sidebar.
    • Workspace switcher at the top-left of the admin sidebar — same pattern as Vercel. Solagon team workspace at the top, every accessible client below it. Switching to a client swaps the sidebar to that client’s scoped tabs (Site insights, Activity, Settings).
    • Per-staff client assignments. Regular admin staff only see clients they’re assigned to in the workspace switcher; superadmins see every client. New clients auto-assign their creator as staff so the workspace shows up immediately.
    • New /admin/clients/[slug] workspace — overview with at-a-glance analytics + monitored sites, plus dedicated pages for analytics (GA4 metrics + chart), uptime (filtered to the client’s sites with open-incident callout), support tickets (filtered to the org), and team/site settings.
    • CLI for assigning staff to clients: npm run admin:assign-staff -- <email> <orgSlug>. Looks up the user in Clerk, creates a staff membership for the given org. Safe to re-run.
    • Both dashboards now run on a shared design system with deep-dark and clean-light themes that you can switch from the user-account menu in the sidebar.
    • Sidebar collapses to an icon rail with a single click — your preference is remembered per dashboard. The collapse toggle moved to a thin strip at the bottom of the sidebar.
    • Sidebar submenus: when you go deeper (e.g. /admin/user-settings/* or /admin/docs/*), the sidebar swaps to that section’s tabs with a "Back to dashboard" link at the top.
    • New /admin/user-settings with sub-pages for account (name + avatar via Clerk), notification preferences (in-app + email toggles per event), and appearance (theme + sidebar default).
    • New /admin/docs — internal-only docs covering architecture, our stack, the third-party tools we depend on (Resend, Clerk, Claude, Vercel, Neon, Microsoft Graph), workflows, and incident-response runbooks.
    • Refined Cmd+K command palette: gradient backdrop with backdrop-blur, larger result rows with hairline icon tiles, animated pop-in. Workspace jumps surface in the palette so you can switch clients without opening the menu.
    • New user-account chip at the bottom of the sidebar shows your name, email, and avatar. Click to access theme toggle, Solagon home, Help, Docs, Changelog, User settings, and Sign out.
    • Notifications popover surfaces new submissions, open support tickets, and monthly reports awaiting review.
    • Every list page in admin and every detail/edit page now uses the new visual system. Same for the client portal’s overview, services, reports, and report-detail pages.
    • Removed the in-app homepage editor and the Web dev clients tab. Case study detail pages now auto-show related articles by service tag.
    • Public changelog at /changelog with code snippets, callouts, and Solagon Team byline.
    • Removed the redundant search button from every TopBar — the sidebar search is the single search surface.
    • Bug fixes: notifications popover no longer overflows the left edge of the sidebar; user-settings page no longer crashes with a server-component function-prop error; useUser() works in admin (added ClerkProvider at the admin layout root); regular staff can now access /admin (auth was previously superadmin-only).

    If you spot anything broken or visually off in the dashboard, ping us in support and we’ll fix it fast.

    Solagon Team

    solagon.com

  31. Six new subdomains: support, docs, status, book, preview, and pay

    A platform of standalone surfaces for clients, prospects, and ourselves — each with its own auth, theme, and chrome.

    middleware.ts — subdomain rewritets
    const SUBDOMAINS = [
      'admin', 'portal', 'support', 'docs',
      'status', 'book', 'pay', 'preview',
    ] as const
    
    export function middleware(req: NextRequest) {
      const host = req.headers.get('host') ?? ''
      const sub = SUBDOMAINS.find((s) => host.startsWith(`${s}.`))
      if (!sub) return NextResponse.next()
      return NextResponse.rewrite(
        new URL(`/${sub}${req.nextUrl.pathname}`, req.url),
      )
    }
    One middleware fans out to every subdomain — each one mapped to a route group with its own layout, theme, and auth.
    • support.solagon.com — clients submit tickets that route to our triage queue with email notifications.
    • docs.solagon.com — long-form engagement docs (pricing, working together, technology) with their own scoped theme.
    • status.solagon.com — public uptime board for the sites we monitor, with incident timelines and visibility scoping.
    • book.solagon.com — scheduling backed by Microsoft 365 calendars and Teams meetings.
    • preview.solagon.com / solagon.dev — branded review URLs (solagon.dev/<slug>) for client deliverables, with optional password gates and view counters.
    • pay.solagon.com — invoice + payment portal for retainer and project billing.
    • Sitewide light/dark theme toggle landed on the marketing site, with the preference persisting across visits and respecting OS settings on first load.
    • Each subdomain ships with its own theme, navigation, and footer — no marketing chrome leakage on subdomain routes.
    • Mobile previews and the marketing footer now feel right on small screens.

    Solagon Team

    solagon.com

  32. Client portal chrome separation and mobile polish

    • Removed the marketing navigation and footer from /portal routes — clients see only the workspace UI, no decorative blurbs.
    • Hidden the Clerk dev badge so production-style screens stop leaking the staging affordance.
    • Industries accordion stacks gracefully on small screens. Stats grid tightened up at narrow widths.
    • Replaced placeholder imagery with real, location-relevant photos on the four city-tagged insights.

    Solagon Team

    solagon.com

  33. Local SEO push and launch readiness

    City pages, structured data, and the changes that took solagon.com from a soft beta to a real launch.

    JSON-LD — ProfessionalService schemajson
    {
      "@context": "https://schema.org",
      "@type": "ProfessionalService",
      "name": "Solagon",
      "url": "https://solagon.com",
      "areaServed": [
        { "@type": "City", "name": "Wilmington, NC" },
        { "@type": "City", "name": "Greenville, NC" },
        { "@type": "City", "name": "Winterville, NC" },
        { "@type": "City", "name": "Ayden, NC" }
      ]
    }
    Schema.org markup tells local search engines exactly which territory we serve.
    • ProfessionalService schema with areaServed scoped to Wilmington, Greenville, Winterville, and Ayden, NC — so local search engines understand the territory we serve.
    • Dedicated landing pages for each city, with local content, schema, and editorial bylines.
    • Removed the under-construction gate — solagon.com is publicly live.
    • Booking and case-study routes wired up.
    • Hardened hero and dashboard layouts so the mobile viewport never scrolls sideways from a stray decorative element.
    • Homepage case-study screenshots added so the audience slider feels grounded in real work.

    Solagon Team

    solagon.com

  34. Homepage redesign with interactive mockups

    A brand-new homepage focused on what we actually build — with mountain backdrops, dental-CRM mockups, and a portal redesign in the same pass.

    • Reworked homepage with interactive product mockups (a dental CRM is the headliner) and mountain backdrops behind every section.
    • Audience slider now sources from admin-managed case studies, so updates flow without code changes.
    • Site-check Safari shell got a scroll-triggered reveal that doesn’t feel gimmicky.
    • Homepage content extracted to lib/homepage-content.ts so the marketing surface has stable, type-safe defaults.
    • Portal redesigned in the same pass: cleaner workspace pages, refreshed typography, and consistent card patterns.

    Solagon Team

    solagon.com

  35. Postgres bootstrap and diagnostics dashboard

    Production content moves to Neon Postgres. A new dashboard tells you exactly what the site is rendering and why.

    Migration: insights table on Postgressql
    CREATE TABLE IF NOT EXISTS insights (
      id          SERIAL PRIMARY KEY,
      slug        TEXT UNIQUE NOT NULL,
      title       TEXT NOT NULL,
      excerpt     TEXT DEFAULT '',
      author      TEXT DEFAULT '',
      date        TEXT DEFAULT '',
      service     TEXT DEFAULT '',
      image       TEXT DEFAULT '',
      content     TEXT DEFAULT '',
      published   BOOLEAN DEFAULT TRUE,
      created_at  TIMESTAMPTZ DEFAULT NOW(),
      updated_at  TIMESTAMPTZ DEFAULT NOW()
    );
    Neon Postgres for production durability, with SQLite as a local-dev fallback.
    • Migrated production content (insights, case studies, testimonials, homepage copy) from SQLite to Neon Postgres for durability and faster cold-starts on Vercel.
    • New /admin/storage diagnostics dashboard shows live database row counts, the bundled-snapshot fallback, and the runtime config the server is using.
    • Bootstrap sync repairs the production DB if a deployment lands ahead of seed data.
    • Trimmed the deployment payload — bundled snapshot is opt-in, ephemeral SQLite stays in /tmp.

    Solagon Team

    solagon.com

  36. First admin dashboard

    A real CMS for everything the marketing site renders.

    • Shipped the first admin dashboard for managing insights, case studies, testimonials, and homepage copy.
    • JWT auth with a long-lived session cookie.
    • Marketing UI overhaul to match the new content surfaces.

    Solagon Team

    solagon.com

  37. Refreshed brand identity

    • Updated typography, color palette, and homepage layout to land in the Solagon brand system.
    • New stats grid, customer testimonials, and a merit carousel landed alongside.

    Solagon Team

    solagon.com

Run the business on software you actually own.

Tell us where the operation drags. We'll come back with what to build, how long it takes, and what it costs to run afterward. Straight answers — even if the answer is no.