Cedarfell Portal
WF-6.12 VD6 Active v1.0

Internal Portal Build Plan

Owner: Roger Thompson · Last updated: 2026-05-13

Implementation plan for the internal portal build and the initial content migration steps.

Amendment 2026-05-29: BI tool is Hex Apps, not Metabase Cloud. See WF-6.9 v1.1 change log for rationale. Phase C step “Metabase Cloud free trial” → “Hex Team trial”. Embedding mechanic (iframe in Astro page, URL stored in Cloudflare Pages env var) is unchanged. Two production dashboards (Daily BC Dashboard live 2026-05-20, Strategic Weekly Review live 2026-05-29) followed this pattern successfully. Substantive rewrite deferred.

WF-6.12 — Internal Portal Build Plan

Type: Implementation plan Value Driver: VD6 Financial Operations (delivery infrastructure) → consumers across VD1–VD5 Version: 1.0 Date: 2026-05-13 Owner: Roger Thompson (build); Claude (pair) — handoff to Vincent/Brandon as consumers when v1 ships Status: Active — WF-6.11 dependency cleared 2026-05-13 Companion to: WF-6.11 Website Infrastructure Decision (stack), WF-6.10 Dashboard Layer Plan (per-dashboard scope)


Purpose

Implementation plan for the internal portal at portal.cedarfell-logistics.com. This document is what Roger and Claude follow during the actual build. It picks up where WF-6.11 left off (stack approved) and stops where WF-6.6 picks up (operational governance once the portal is live).


Scope

In scope:

Out of scope:


Preconditions

Before kicking off the build:

#PreconditionOwnerStatus
1WF-6.11 Website Infrastructure Decision approvedRogerPending sign-off
2WF-6.9 Dashboard Tool Decision approvedRogerApproved 2026-05-13
3Domain cedarfell-logistics.com owned + DNS accessRogerConfirmed — DNS on Google Cloud DNS
4Google Workspace + Cloud Identity directory in placeRogerConfirmed — Roger/Vincent/Brandon on Workspace, Aaliyah on Cloud Identity
5Cloudflare account createdRogerTBD — single action at https://dash.cloudflare.com/sign-up
6Metabase Cloud free trial started (for first iframe)RogerTBD — kicks off at the dashboard embedding step
7Tensor emails for Jana + Sheryl collectedAaliyah / RogerTBD — Cloudflare Access exception list input

Route inventory

The portal opens with six top-level routes. Each is described below with its content source, build cost, and dependencies.

RoutePurposeContent sourceBuild costNotes
/Team home — single bookmark for the teamHand-authored landing page0.5 daySurface today’s key links: Daily BC Dashboard, Open SOPs, Recent Decisions, Help
/dashboardsEmbedded Metabase dashboardsMetabase iframes per WF-6.100.5 day for shell; per-dashboard cost in WF-6.10Iframe-only at launch (Starter tier); interactive embed at Pro tier (5+ CSAs)
/sopsSOP corpus rendered as web pages/VD*/Workflows/*.md (Cowork)1 day for collection schema + rendering; ongoing as SOPs are writtenAstro content collections; index by VD; full-text search optional v1.1
/kpisKPI Taxonomy as browsable site/Strategic/KPI-Taxonomy-v1.1.md0.5 dayRender the 115 KPIs with filters by VD, layer, readiness state
/decisionsDecision Catalogue + Decisions-Log/VD6 - Financial Operations/Data/Decision-Catalogue.md, Decisions-Log.md0.5 daySearchable archive; reverse-chronological default
/handbookOnboarding + role pagesNet-new content authored in markdown1 day for shell; per-page content over timeTeam roster, system inventory, compensation framework, escalation paths

Total v1 build cost (route layer): ~4 days of Roger/Claude pair-build time, not counting per-dashboard work.

Optional v1.1+ routes

RoutePurposeTrigger
/changelogAuto-generated from Decisions-Log entriesAfter 50+ decisions logged
/searchFull-text search across all portal contentIf BCs report scanning friction
/adminRoger-only admin links (Cloudflare, MotherDuck, Metabase)Day 1 if Roger wants it

Content migration approach

The non-obvious advantage of Astro for Cedarfell: the existing SOP corpus migrates with zero conversion.

How Astro reads /VD*/Workflows/*.md

Astro’s content collections accept any glob pattern. The portal repo (cedarfell-portal) will reference the SOP files in place rather than copy them. Two options:

Option 1 — Git submodule (recommended). The portal repo includes the Cowork workspace as a submodule. Astro’s content config globs into /cowork-workspace/VD*/Workflows/*.md. SOPs stay in Cowork as the source of truth; portal builds pull the latest submodule pointer at build time. Trade-off: requires submodule update + portal rebuild after SOP changes (5-minute manual step or scheduled daily build).

Option 2 — Build-time sync script. A pre-build script scripts/sync-content.mjs copies /VD*/Workflows/*.md into src/content/sops/ before Astro builds. Faster local dev; requires running the sync script. Cloudflare Pages can run this in the build command.

Decision for v1: start with Option 2 (build-time sync). Simpler operationally; no submodule mental overhead. Revisit submodules if multi-CSA scales create coordination friction.

Front matter expected by Astro

Astro content collections validate front matter against a Zod schema declared in src/content/config.ts. The schema for SOPs:

const sopsCollection = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    valueDriver: z.enum(['VD1', 'VD2', 'VD3', 'VD4', 'VD5', 'VD6', 'Strategic']),
    workflowId: z.string().regex(/^WF-\d+\.\d+/),
    owner: z.string(),
    version: z.string(),
    status: z.enum(['Draft', 'Active', 'Archived']),
    lastUpdated: z.date(),
  }),
});

The Cedarfell SOPs (e.g., WF-4.4 Monthly Driver Scorecard SOP.md) already encode this metadata in their headers but not as YAML front matter. Migration step: add YAML front matter blocks to each SOP file in Cowork. One-time pass; ~30 minutes total for the existing corpus.

Example:

---
title: Monthly Driver Scorecard SOP
valueDriver: VD4
workflowId: WF-4.4
owner: BC on duty
version: 1.0
status: Active
lastUpdated: 2026-04-29
---

The body of each SOP stays untouched. Astro renders the markdown as HTML with theme-applied typography.

Rendering for human consumption

The /sops route shows:

This is build-time work — no runtime JavaScript needed.

KPI Taxonomy rendering

The single KPI-Taxonomy-v1.1.md file (115 KPIs in one document) is more useful as a structured browsable site than a single long page. Migration step: a one-time pre-build script scripts/parse-kpis.mjs parses the markdown into a JSON file (src/data/kpis.json) with one record per KPI. Astro renders the JSON via a dynamic route /kpis/[id].astro for detail and /kpis/index.astro for the indexable list.

Schema per KPI record:

{
  "id": "OP.VD4.10",
  "name": "VEDR confirmed events per driver",
  "layer": "Operational",
  "valueDriver": "VD4",
  "formula": "...",
  "owner": "BC on duty",
  "cadence": "Monthly",
  "dataSource": "vedr_events",
  "readiness": "P",
  "target": "...",
  "decisionsSupported": ["..."]
}

Decisions rendering

Same shape as KPIs. Parse Decisions-Log.md table rows into JSON; render as /decisions/index.astro (reverse-chronological table) and /decisions/[date]/[slug].astro (detail page per decision).


Authentication setup

Cloudflare Access policy

The portal subdomain portal.cedarfell-logistics.com is registered as a Cloudflare Access protected application. Configuration:

Identity provider. Single provider: Google. Cloudflare Access supports Google as a native OIDC integration. Setup:

  1. In Cloudflare Zero Trust dashboard → Settings → Authentication → Add new
  2. Provider type: Google
  3. Authorize Cloudflare to read identity from cedarfell-logistics.com Google directory (Roger consents during setup)
  4. Save

This works for both Workspace users (Roger, Vincent, Brandon) and Cloud Identity users (Aaliyah) — they’re the same Google directory under the hood.

Access policy. One application: portal.cedarfell-logistics.com. One policy attached: “Cedarfell team + Tensor bookkeepers.”

Policy rules:

Effect: anyone hitting portal.cedarfell-logistics.com gets redirected to a Google sign-in page. After signing in with an @cedarfell-logistics.com address or one of the two whitelisted Tensor emails, they get the portal. Anyone else gets an “Access denied” screen.

Session duration. 24 hours default. Configurable per-policy if we want longer/shorter.

Break-glass access. Roger configures a service token in Cloudflare Access (Zero Trust → Access → Service Tokens) that grants emergency access if Google OIDC is down. Token is rotated annually; stored in Roger’s password manager. Never used unless real emergency.

User onboarding

When a user accesses portal.cedarfell-logistics.com for the first time:

  1. Cloudflare Access redirects to Google sign-in.
  2. User selects their Google account (or signs in if not in browser).
  3. Google returns identity to Cloudflare.
  4. Cloudflare matches against policy rules.
  5. If match → 24-hour JWT cookie set → user lands on portal.
  6. If no match → Access denied screen with support email (Roger’s address).

No new accounts created. No new passwords. Users sign in with the Google account they already use for email.

Roster confirmed (2026-05-13)

PersonIdentity typeEmailAccess
Roger ThompsonWorkspaceroger.thompson@cedarfell-logistics.comFull
Vincent TabletWorkspacevincent.tablet@cedarfell-logistics.comFull
Brandon FankhauserWorkspacebrandon.fankhauser@cedarfell-logistics.comFull
Aaliyah MonetCloud Identityaaliyah@cedarfell-logistics.comFull
Jana (JSJ Consultants — Tensor’s US bookkeeping arm)Externaljana@jsjconsultants.comFull (financial dashboards primary use)
Sheryl (JSJ Consultants — offshore bookkeeping)Externalsheryl@jsjconsultants.comFull (financial dashboards primary use)
Sanjay (Tensor.Financial — account manager)Externalsanjay@tensor.financialFull (relationship oversight)

Aaliyah’s access status revisits when she migrates to Swan Island. Likely outcome: she keeps the email + portal access until the operational transition completes, then either retains as cross-entity admin or rotates off.

Mechanic + driver access (deferred)

Mechanics (Isaac, Brian) and drivers don’t get portal access at v1. Mechanic-facing dashboards (PM compliance, fleet uptime) can be added in v2 if Roger sees value. Drivers consume the monthly scorecard via printed page (per WF-4.4), not the portal.


Dashboard embedding strategy

Detailed per-dashboard scope is in WF-6.10. This section covers the embedding mechanic — how Metabase content shows up inside the portal.

Two embedding modes (from WF-6.9)

Static iframe (Metabase Cloud Starter, 1 CSA). Each dashboard gets a public-with-token iframe URL that the portal embeds in a <iframe> tag. No interactivity from inside the portal — filters, drill-downs, and time-range changes require the user to open Metabase directly in a new tab. BCs see read-only snapshots refreshed at the underlying dashboard’s cache interval.

Interactive embed (Metabase Cloud Pro, 5+ CSAs). Cloudflare’s JWT can be passed to Metabase via signed embedding URLs; users get full interactivity (filters, drill-downs, custom date ranges) inside the iframe with row-level security applied based on identity. Adds ~1 week of integration work at upgrade time.

v1 decision: static iframes

For 1-CSA scale and Metabase Starter, static iframes are sufficient. Each /dashboards/<name> route is essentially:

---
import Layout from '../../layouts/Portal.astro';
---
<Layout title="Daily BC Dashboard">
  <iframe
    src="https://cedarfell.metabaseapp.com/public/dashboard/<token>"
    frameborder="0"
    width="100%"
    height="900"
    allowtransparency
  />
</Layout>

The iframe URL token is rotated when a dashboard is materially changed. Tokens are stored in Cloudflare Pages environment variables (not committed to git) so they don’t end up in public version control. Pages env vars are accessible at build time only.

Dashboard refresh cadence

Metabase Cloud refreshes dashboard data on a configurable schedule per question (default: every hour for SQL questions). For daily-focused dashboards (Daily BC, Daily Owner), we set refresh to every 15 minutes during morning hours (5 AM – 10 AM Pacific) and hourly the rest of the day. For weekly/monthly dashboards, once per day at 4 AM is sufficient. Details in WF-6.10.


Build sequence

The portal builds incrementally over four phases. Each phase produces a working artifact that the team can use before the next phase starts.

Phase A — Scaffold (Day 1)

Goal: Empty portal at portal.cedarfell-logistics.com rendering a single landing page, behind Cloudflare Access.

Steps:

  1. Roger creates cedarfell-portal repo on GitHub.
  2. Roger creates Cloudflare account (if not already) at https://dash.cloudflare.com/sign-up.
  3. Roger creates Cloudflare Pages project: connect to cedarfell-portal GitHub repo; framework preset = Astro; auto-deploy main branch.
  4. Roger or Claude scaffolds Astro project: npm create astro@latest with minimal template.
  5. Author placeholder src/pages/index.astro (“Cedarfell Portal — under construction”).
  6. Push to main; verify Cloudflare Pages auto-builds and serves at <project>.pages.dev.
  7. Add custom domain portal.cedarfell-logistics.com in Cloudflare Pages settings.
  8. DNS step (see DNS Migration section): add CNAME for portal subdomain pointing at the Pages project. Initially this is added in Google Cloud DNS (until full nameserver migration in Phase D).
  9. In Cloudflare Zero Trust dashboard, add Google identity provider.
  10. Create Cloudflare Access application for portal.cedarfell-logistics.com; attach the policy described above.
  11. Roger tests: visit portal.cedarfell-logistics.com from incognito → Google sign-in → portal landing page renders.
  12. Have Vincent + Brandon test their sign-in independently.

Exit criteria: Roger, Vincent, Brandon, Aaliyah can all sign in. Anyone else gets denied. Page loads in under 2 seconds.

Time estimate: 0.5 day for Roger; 0.5 day Claude pair.

Phase B — First three SOPs as proof (Day 2)

Goal: Validate the content migration approach on three real SOPs.

Steps:

  1. Add YAML front matter to three SOPs in Cowork (suggest: WF-2.1 Morning BC Protocol, WF-4.4 Monthly Driver Scorecard, WF-6.4 Scorecard Data Acquisition Plan — one from VD2, one from VD4, one from VD6).
  2. In cedarfell-portal repo, scaffold src/content/config.ts with the SOP collection schema described above.
  3. Implement scripts/sync-content.mjs to copy /VD*/Workflows/*.md from the Cowork workspace into src/content/sops/.
  4. Implement src/pages/sops/index.astro (list view) and src/pages/sops/[...slug].astro (detail view).
  5. Configure Cloudflare Pages build command to run the sync script before npm run build. (Cloudflare Pages can mount the Cowork workspace via a git submodule or via a published mirror — to be decided in implementation; the simplest path for v1 is a published mirror.)
  6. Push; verify /sops page lists three entries; click into each renders correctly.
  7. Roger + Vincent + Brandon spot-check rendering on phones.

Exit criteria: three SOPs render correctly; navigation works; mobile readable.

Time estimate: 1 day for Roger; 1 day Claude pair.

Phase C — First dashboard embed (Day 3–4)

Goal: Daily BC Dashboard live at /dashboards/daily-bc.

Steps:

  1. Roger starts Metabase Cloud free trial (per WF-6.9 negotiation point #2): 14 days to validate.
  2. Connect Metabase to MotherDuck cedarfell database via JDBC.
  3. Build the Daily BC Dashboard in Metabase per WF-6.10 spec (questions, layout, refresh schedule).
  4. Generate the public-with-token URL.
  5. Add the iframe page in cedarfell-portal: src/pages/dashboards/daily-bc.astro.
  6. Update src/pages/dashboards/index.astro (list view) with link to Daily BC.
  7. Push; verify iframe renders inside the portal at portal.cedarfell-logistics.com/dashboards/daily-bc.
  8. Vincent + Brandon test for one morning cycle.

Exit criteria: Daily BC Dashboard renders in portal; BCs validate the data matches what they’d see opening Metabase directly.

Time estimate: 1.5 days for Roger; 1.5 days Claude pair.

Phase D — Expand and migrate DNS (Day 5–7)

Goal: Remaining v1 routes operational; nameservers moved to Cloudflare for full Cloudflare-native control.

Steps:

  1. Add YAML front matter to the remaining SOPs in Cowork (~30 min).
  2. Build /kpis route with the KPI Taxonomy parsed from /Strategic/KPI-Taxonomy-v1.1.md.
  3. Build /decisions route with the Decisions-Log parsed.
  4. Author /handbook content — at minimum, team roster, system inventory, escalation paths.
  5. Author / (portal home) with quick links to today’s key resources.
  6. Migrate nameservers from Google Cloud DNS to Cloudflare (detail below).
  7. Verify everything still works after DNS migration.

Exit criteria: all six routes live; team can use the portal as the daily single-bookmark home.

Time estimate: 2 days for Roger; 2 days Claude pair.

Total v1 build time: ~5 days of Roger time over ~2 weeks elapsed (DNS propagation and BC testing add elapsed time without consuming build time).


DNS migration plan

Current state (confirmed 2026-05-13)

Target state

Migration steps (no outage if done in this order)

  1. Create Cloudflare account + add domain (5 min)
    • In Cloudflare dashboard → “Add a Site” → enter cedarfell-logistics.com
    • Cloudflare scans existing DNS records and imports them automatically
    • Cloudflare provides two assigned nameservers (e.g., ada.ns.cloudflare.com and aiden.ns.cloudflare.com)
  2. Verify imported records (5 min)
    • Confirm the www CNAME (Squarespace) and any apex records imported correctly
    • Do NOT delete any records yet
  3. Add new portal subdomain CNAME (2 min)
    • In Cloudflare DNS → add CNAME for portal pointing at the Cloudflare Pages project URL
    • Set proxied (orange cloud) so Cloudflare Access can gate it
  4. Set TTLs low at the current DNS provider (5 min)
    • In Google Cloud DNS, reduce TTLs on existing records to 300 seconds (5 min) before the switch — propagation in case of rollback is fast
    • Wait 24–48 hours for low TTLs to fully propagate
  5. Switch nameservers (5 min + propagation)
    • In Squarespace Domains console (or wherever the domain registrar lives) → update nameservers to Cloudflare’s two assigned NS records
    • Propagation: typically 1–4 hours for most users, up to 24 hours for stragglers
  6. Monitor propagation (during 1–24 hours)
    • Check at https://www.whatsmydns.net/ that resolvers return Cloudflare nameservers globally
    • Verify existing www.cedarfell-logistics.com (Squarespace site) still resolves correctly during transition
    • Verify portal.cedarfell-logistics.com resolves to Cloudflare Pages once nameservers propagate
  7. Test Cloudflare Access on portal (30 min)
    • Sign-in flow works
    • Roster of allowed users tested
    • Deny screen for unauthorized users tested
  8. Decommission Google Cloud DNS zone (cleanup, 5 min)
    • Once Cloudflare is authoritative and stable for a week, the Google Cloud DNS zone for cedarfell-logistics.com can be removed
    • Optional; leaving it doesn’t cause harm

Total elapsed time: 1–3 days. Total active work: ~1 hour.

Rollback plan

If anything breaks during nameserver migration:

  1. In Squarespace Domains console → revert nameservers back to Google Cloud DNS (ns-cloud-eN.googledomains.com)
  2. Propagation back to old state: 1–4 hours
  3. Investigate the failure in a non-production environment before re-attempting

The low TTLs set in step 4 ensure rollback propagates fast.


Squarespace site cutover

There’s currently a live Squarespace-hosted site at www.cedarfell-logistics.com. The new architecture replaces it. Two cutover paths:

Option 1 — Cutover at Phase D nameserver migration

Build the external Astro site (/, /customers, /vendors, /careers, /about) in the same cedarfell-portal repo (or a sibling repo cedarfell-public). Cloudflare Pages serves both: apex and www. go to the new Astro site, portal. goes to the protected portal.

When nameservers cut to Cloudflare in Phase D, the apex/www traffic starts going to the new Astro site, and the Squarespace site stops getting visitors. The Squarespace subscription can be canceled after 30 days of clean traffic data.

Trade-off: the new external site has to be ready by Phase D. If external site content isn’t ready, the apex/www would show a barebones Astro placeholder.

Option 2 — Cutover later (defer external site)

Keep Squarespace serving the apex/www in the new Cloudflare DNS. The CNAME stays www → ext-sq.squarespace.com; apex stays however it’s configured today. Only portal.cedarfell-logistics.com is new and Cloudflare-served.

When the external Astro site is ready (separate workstream), update the DNS records to point at Cloudflare Pages for the external project and decommission Squarespace.

Trade-off: Squarespace subscription continues (~$200/yr) until external site is ready. Two DNS zones to maintain temporarily.

Decision for v1: Option 2. Portal is the priority; external site is parallel work; don’t gate portal launch on external site readiness. Roger’s call.


Per-CSA replication recipe

A new CSA owner clones the portal in under half a day. Step-by-step:

  1. Clone the template repo (git clone cedarfell-portal csa-name-portal)
  2. Repoint content sources in scripts/sync-content.mjs to the new CSA’s Cowork workspace path or content mirror
  3. Update site URL in astro.config.mjs (e.g., site: 'https://portal.csa-name.com')
  4. Update brand assets in public/ (logo, favicon, theme colors in Tailwind config)
  5. Create Cloudflare account for new CSA (or invite into existing parent organization if portfolio is managed centrally)
  6. Create Cloudflare Pages project: connect to the new GitHub repo
  7. Add custom domain portal.csa-name.com
  8. Configure Cloudflare Access: identity provider = Google (the new CSA’s Google directory); policy = @csa-name.com allowed + Tensor exception list
  9. Subscribe to Metabase Cloud Starter for the new CSA (separate $1K/yr per WF-6.9)
  10. Connect Metabase to the new CSA’s MotherDuck database
  11. Rebuild dashboards for the new CSA’s data, or clone via Metabase JSON export
  12. Update dashboard iframe URLs in src/pages/dashboards/*.astro
  13. DNS: point new domain’s nameservers at Cloudflare
  14. Test full sign-in flow with new CSA team members

Total per-CSA setup: ~4 hours of work; ~24 hours elapsed time including DNS propagation and team testing.

Total per-CSA cost: $0 hosting + $0 auth (until 50 users) + Metabase Cloud ($1K/yr) + domain ($15/yr).


Open dependencies

These items aren’t blockers for the build but must be resolved before portal launch:

#ItemOwnerTriggerResolution path
1Tensor exact email addresses for Jana + SherylAaliyah/RogerBefore Phase A Cloudflare Access policy createdAaliyah asks Tensor next time she’s in contact
2Cloudflare account creationRogerPhase A Day 1One-time signup at dash.cloudflare.com/sign-up
3Metabase Cloud free trial startRogerPhase C Day 3One-time signup at store.metabase.com
4YAML front matter added to existing SOPsRogerPhase B Day 2~30 minutes in Cowork; one-time pass
5Content mirror or submodule strategy for /VD*/Workflows/*.mdRoger + ClaudePhase B Day 2Implementation decision during Phase B; published mirror is simplest
6Cutover decision for Squarespace siteRogerPhase D Day 7Confirm Option 2 (defer external site) holds; decision can be revisited later
7DNS migration timingRogerPhase D Day 5–7Run during low-traffic period (typical low traffic: weekends or evenings Pacific)

Risk register

#RiskLikelihoodImpactMitigation
1DNS migration causes brief outage of existing Squarespace siteLowMediumLow TTLs pre-cut; rollback path documented; staged during low-traffic window
2Cloudflare Access policy mistakenly excludes a real userMediumMediumRoster confirmed in writing 2026-05-13; staged user testing in Phase A Day 1
3Metabase iframe URL leaks publicly and exposes dataLowHighIframe URLs stored in Cloudflare Pages env vars (not git); rotated on material dashboard changes; portal route is Cloudflare-Access-gated anyway
4SOP front matter changes break Cowork’s normal workflow for RogerLowLowYAML front matter is invisible in markdown rendering and standard for static site generators; doesn’t interfere with Cowork
5Build script can’t read /VD*/Workflows/*.md from Cowork in CIMediumMediumImplementation decision in Phase B: use a published content mirror to a GitHub repo Cloudflare Pages can clone, OR use a build hook that pushes content on Cowork-side changes
6BC adoption fails — they keep opening individual files instead of using portalMediumHighDaily BC Dashboard is the portal’s first compelling feature; bookmark replacement (portal.cedarfell-logistics.com as morning home page) is the behavior change
7Cloudflare Pages free tier exceededLowLowAt 1–5 CSA scale, well within the 500-builds/mo and unlimited bandwidth tiers
8Mobile rendering bug surfaces post-launchMediumLowTailwind responsive defaults handle most cases; Roger spot-checks on phone after each phase
9Google directory access revoked or scoped incorrectlyLowMediumCloudflare Access supports multiple IdPs; if Google directory access is lost, add a backup OIDC provider
10Existing Squarespace site has hidden content that the team usesLowLowTake an inventory of the current Squarespace site during Phase A; migrate any actively-used content to the new external site before cutover

Measurable outcomes

Portal v1 ships when:

MetricTargetMeasurement
All six v1 routes live6 of 6Manual checklist
All four Cedarfell users + 2 Tensor users can sign in6 of 6Roster test pass
Daily BC Dashboard renders inside portalYesVincent + Brandon validate morning of
Average page load time< 2 secondsCloudflare Pages analytics
BC daily usage≥ 5 portal visits per BC per week within 30 daysCloudflare Access analytics

Portal v1 is “successful” 30 days after launch if BC usage hits target. If not, the friction is investigated (not the portal abandoned) — usage gap is a signal that something specific in the portal isn’t earning the bookmark.


Cross-references


Change log

VersionDateAuthorChange
1.02026-05-13Roger Thompson (with Claude)Initial plan. Six v1 routes, four-phase build sequence, DNS migration plan from Google Cloud DNS to Cloudflare, Squarespace cutover Option 2 (defer external site), per-CSA replication recipe documented. Roster confirmed: Roger / Vincent / Brandon / Aaliyah at @cedarfell-logistics.com; Jana + Sheryl via Cloudflare Access exception list.

Appendix A — File and folder layout

cedarfell-portal/                       (new repo)
├── astro.config.mjs                     site config
├── tailwind.config.cjs                  styling
├── package.json
├── scripts/
│   ├── sync-content.mjs                 pulls /VD*/Workflows/*.md
│   ├── parse-kpis.mjs                   KPI markdown → JSON
│   └── parse-decisions.mjs              Decisions-Log → JSON
├── public/
│   ├── favicon.svg
│   └── logo.svg
├── src/
│   ├── content/
│   │   ├── config.ts                    Zod schemas
│   │   ├── sops/                        copied from Cowork at build
│   │   └── handbook/                    handbook pages
│   ├── data/
│   │   ├── kpis.json                    generated at build
│   │   └── decisions.json               generated at build
│   ├── layouts/
│   │   └── Portal.astro                 shared shell
│   ├── components/
│   │   ├── Nav.astro
│   │   └── Footer.astro
│   └── pages/
│       ├── index.astro                  /
│       ├── dashboards/
│       │   ├── index.astro              /dashboards
│       │   ├── daily-bc.astro
│       │   ├── daily-owner.astro
│       │   ├── weekly.astro
│       │   ├── monthly-scorecard.astro
│       │   ├── monthly-pl.astro
│       │   ├── quarterly.astro
│       │   └── annual.astro
│       ├── sops/
│       │   ├── index.astro              /sops
│       │   └── [...slug].astro          /sops/wf-4-4 etc.
│       ├── kpis/
│       │   ├── index.astro              /kpis
│       │   └── [id].astro               /kpis/OP.VD4.10 etc.
│       ├── decisions/
│       │   ├── index.astro              /decisions
│       │   └── [date]/[slug].astro
│       └── handbook/
│           ├── index.astro              /handbook
│           └── [...slug].astro
└── README.md

Appendix B — Cloudflare Access policy reference

For copy-paste into the Cloudflare Zero Trust dashboard:

Application configuration:

Policy configuration:

Identity provider:


Appendix C — Day-by-day plan

The four phases mapped to elapsed days:

DayPhaseRoger actionsClaude actionsVincent/Brandon actions
1A scaffoldCloudflare signup, GitHub repo, Pages project, Access policy, first testPair through scaffold, write initial Astro configTest sign-in once policy live
2B SOPsAdd YAML front matter to 3 SOPs, pushBuild content collection, sync script, list + detail pagesClick through /sops, confirm rendering
3C dashboardStart Metabase trial, build Daily BC dashboard in MetabaseAdd iframe page to portal, push(none — Metabase building)
4C dashboardValidate Daily BC dashboard data with Vincent + BrandonRefine iframe layout, mobile checkSpend a real morning using it
5D expandAdd front matter to remaining SOPsBuild /kpis, /decisions, /handbook, /Spot-check all routes
6D expandPre-DNS-migration: lower TTLs in Google Cloud DNSFinal mobile and accessibility review(none)
7D migrateMigrate nameservers to Cloudflare, monitor propagation, decommission test pathVerify all routes work post-migration(none — propagation hours)

Beyond Day 7: WF-6.6 Dashboard Operations SOP defines the ongoing governance model (who edits what, how changes flow, refresh policies).