Marketing Ops Wiki + Dashboard
Build an AI-governed marketing ops system with Supabase and Lovable.
Introduction
30 Second Summary
Marketing teams drown in scattered reports, lost decisions, and tribal knowledge that lives in someone's head. When your team asks "how is that campaign performing?" or "why did we make that call?", the answer should live in a system, not a Slack thread.
In this project, you will build a marketing operations foundation that combines the Karpathy LLM Wiki methodology with a production database and web dashboard. Your team will be able to submit reports via Slack, track KPIs, and maintain a governed knowledge base.
What You'll Build
A marketing operations command center with structured file architecture in GitHub, a PostgreSQL database in Supabase, a web dashboard built in Lovable, and a Slack integration for team reporting.
By the end of this project, you'll have:
- A structured wiki file architecture in GitHub following the Karpathy LLM Wiki methodology, with a CLAUDE.md schema, document templates, and a governed directory structure maintained by Claude Code.
- A live Supabase database with tables for strategic fronts, time-series KPI measurements, decision logs, experiment tracking, IC reports, and Google Drive creative asset metadata.
- A Lovable-generated dashboard connected to Supabase for viewing KPIs, registering ICs, uploading reports, browsing creative assets, and a Slack slash command (/report) for IC data intake.
- Secret Mission: Build an automated KPI health check that flags metrics trending below target and sends a Monday morning summary to Slack.
What do I need before starting?
You'll need a Lovable account (Pro plan recommended at $25/month), a Supabase account, a GitHub account, Claude Code with a Max license, and admin access to a Slack workspace. You should also have Obsidian installed and a Google Drive account for creative asset references.
Set Up Accounts and Repository
This project spans three platforms that each handle a different layer of your marketing operations system. GitHub stores your wiki files and code. Supabase provides the database, authentication, and serverless functions. Lovable generates your dashboard UI and connects everything together.
Before you write any code, these three platforms need to be linked. In this step, you will create a Lovable project, connect it to GitHub (which creates your repository), set up a Supabase project, and wire them all together so changes flow between them automatically.
In this step, get ready to:
- Create a Lovable project and connect it to GitHub for two-way sync.
- Set up a Supabase project and note your database credentials.
- Clone your repository locally so Claude Code can work on it.
Create your Lovable project and connect GitHub
Lovable generates your dashboard UI and manages your frontend code. When you connect a Lovable project to GitHub, it automatically creates a new repository and starts two-way sync. This means you cannot connect to a pre-existing repo. Lovable always creates a fresh one.
- Head to lovable.dev and open your dashboard.
- In the prompt input, type a short description of what you want to build. Enter something like:
Marketing operations dashboard with KPI tracking, IC registry, report uploads, and creative asset browser. Connect to Supabase for backend.
- Press Enter to let Lovable generate a starting point for your project.
- Wait for the initial scaffold to finish generating. This may take 30-60 seconds.
What does this initial prompt do?
Lovable uses your prompt to scaffold a React + Tailwind application. You do not need a perfect description right now. You will refine the dashboard extensively in a later step. This just gives Lovable enough context to set up a sensible project structure.
Once your project is generated, connect it to GitHub to create your repository.
- Click your project name at the top left of the editor, then select Settings.
- Select Git, then GitHub.
- Click Add connection if you have not connected your GitHub account to this Lovable workspace before.
- A GitHub popup will appear asking you to install and authorize the Lovable GitHub app. Choose your GitHub account, select All repositories, and click Install & Authorize.
- Back in Lovable, click Connect next to your GitHub account.
Lovable creates a new GitHub repository and starts two-way sync automatically. You should see the repository status change to Connected with clone URLs visible.
Why does Lovable create the repo?
Lovable manages the repository structure to ensure two-way sync works reliably. Changes you make in Lovable push to GitHub, and changes made locally (via Claude Code) pull back into Lovable. This keeps all three tools working from the same source of truth.
Not seeing the GitHub popup?
Make sure your browser allows popups from lovable.dev. If you already have the Lovable GitHub app installed on your account from a previous project, you may skip straight to the Connect button without seeing a popup.
help me connect my Lovable project to GitHub
Create your Supabase project
Supabase provides your PostgreSQL database, Row Level Security, and Edge Functions. You need a project created before you can connect it to Lovable.
- Open database.new in your browser. This takes you directly to the new project form in the Supabase dashboard.
- Enter a project name. Use something descriptive like marketing-ops.
- Set a strong database password and save it somewhere secure. You will need this password later.
- Select the region closest to your team from the dropdown.
- Click Create new project.
- Wait for the database to finish launching. This typically takes 1-2 minutes.
Once your project is ready, note your Project URL and anon key. You will need these to connect Lovable to Supabase.
- In your Supabase project dashboard, click the Connect button near the top of the page.
- Copy your Project URL (the base URL for all API requests). Paste it into a temporary note.
- Copy the anon key (or the publishable key if your project uses new-format keys). Paste it into the same note.
What is the anon key?
The anon key is a public client-side key. It is safe to include in frontend code because Supabase protects your data with Row Level Security policies, not by hiding the key.
Supabase is transitioning to new key formats (sb_publishable_...). If your project shows a publishable key instead of an anon key, use the publishable key. They serve the same purpose.
Cannot find the Connect button or API keys?
You can also find your keys by navigating to Settings in the left sidebar, then API Keys. The Project URL and keys are listed there under the appropriate tabs.
help me find my Supabase project URL and anon key
Connect Supabase to Lovable and clone your repository
With both projects created, the final piece is wiring Supabase into Lovable and cloning your repository so Claude Code can work on the files locally.
- Switch back to your Lovable project in the browser.
- Open Project Settings (click the project name at the top left, then Settings).
- Select Integrations, then click Supabase.
- Click Connect Supabase.
- Paste your Supabase Project URL and anon key (or publishable key) into the connection form.
- Confirm the connection. Lovable should display a success message showing it is linked to your Supabase project.
What does this connection enable?
Once connected, Lovable can generate UI components that read from and write to your Supabase database directly. When you prompt Lovable to build a KPI dashboard or report upload form later, it will query your actual database tables.
Now clone your GitHub repository locally so Claude Code can maintain the wiki architecture and backend logic.
- In your Lovable project settings under Git > GitHub, find the HTTPS clone URL displayed for your repository.
- Copy the HTTPS clone URL.
- Open your terminal. Press Cmd+Space (macOS) or the Windows key (Windows) and type Terminal, then press Enter.
- Move to your Desktop by running this command:
cd ~/Desktop
- Clone your repository by running this command:
git clone [[YOUR_REPO_URL="https://github.com/your-username/your-lovable-repo.git"]]
- Confirm the clone worked by running:
ls
You should see your repository folder listed in the output. This is the folder Claude Code will use to build your wiki architecture in the next step.
Clone failed or permission denied?
Make sure you are using the HTTPS URL (starting with https://github.com/), not the SSH URL. If prompted for credentials, use a GitHub Personal Access Token as your password. You can generate one at github.com/settings/tokens.
help me clone my GitHub repository
Your foundation is in place. Three platforms are linked and ready to work together. Next up, you will use Claude Code to build the wiki file architecture that gives your system its persistent memory.
Build the Wiki File Architecture
Your accounts are linked and your repo is ready. Now it's time to build the knowledge layer that makes this system intelligent.
The Karpathy LLM Wiki methodology gives AI tools a persistent, structured memory by organizing knowledge into layered markdown files. Instead of starting from zero each session, Claude Code reads a schema file (CLAUDE.md) that tells it exactly how to maintain your marketing operations knowledge base.
In this step, get ready to:
- Create the wiki directory structure following the Karpathy methodology.
- Write the CLAUDE.md schema that governs how Claude Code maintains the wiki.
- Set up the content catalog and action log files.
Create the wiki directory structure
The wiki methodology separates content into layers. Raw source documents go in one place, compiled wiki pages go in another, and generated outputs live separately. This separation keeps Claude Code from accidentally overwriting source material when it compiles knowledge.
- Open your terminal and navigate to your cloned repository folder.
- Start Claude Code by running:
claude
- Once the Claude Code prompt appears, give it this instruction to create the full directory structure:
Create the following directory structure in this repo for a marketing operations wiki:
- raw/
- wiki/ (with subdirectories: strategy/, kpis/, ics/, campaigns/, decisions/, experiments/)
- output/
- _meta/
- templates/
Create each directory with a .gitkeep file so Git tracks the empty folders.
What does this structure do?
- raw/ stores source documents (IC reports, meeting notes). These are immutable once ingested.
- wiki/ is the LLM-compiled knowledge base. Claude Code maintains these pages automatically.
- output/ holds generated reports, slides, and charts.
- _meta/ stores compile state, health check results, and configuration.
- templates/ contains document templates for IC reporting and data intake.
- Approve each file creation when Claude Code asks for permission.
- After Claude Code finishes, verify the structure exists by running ls in the repo root (you can ask Claude Code to do this, or exit and run it yourself).
You should see raw, wiki, output, _meta, and templates directories listed alongside your existing README.md.
Directories not showing up?
- Make sure you approved each change when Claude Code proposed it. If you declined, run claude again and re-issue the instruction.
- Confirm you started Claude Code from inside your cloned repository folder, not a parent directory.
Help me troubleshoot why Claude Code didn't create my directory structure.
Create the CLAUDE.md schema file
The CLAUDE.md file is special. Claude Code reads it at the start of every session to understand how your project works. It defines ingestion workflows, naming conventions, and cross-referencing rules for your marketing wiki.
- In your Claude Code session, give it this instruction to create the schema file:
Create a CLAUDE.md file in the repo root. This is the schema file that defines how you maintain the marketing operations wiki. Include sections for: Architecture (describing each directory), Naming Conventions (kebab-case files, ISO 8601 dates, KPI ID format), Ingestion Workflow (7-step process for new sources), IC Report Ingestion (6-step process), Health Checks (5 checks to run periodically), Cross-Reference Rules (wiki link conventions), and Output Formats.
Why give Claude Code a detailed prompt?
The more specific your prompt, the closer Claude Code's output will match what you need. Claude Code will propose the full file content and ask for your approval before writing it.
If Claude Code's proposed content doesn't match your needs, you can ask it to revise specific sections before approving.
- Review Claude Code's proposed CLAUDE.md content and approve the file creation.
- Verify the file exists by checking that CLAUDE.md appears in your repo root alongside the README.md.
Open CLAUDE.md and confirm it contains the Architecture, Naming Conventions, Ingestion Workflow, IC Report Ingestion, Health Checks, Cross-Reference Rules, and Output Formats sections.
What makes a good CLAUDE.md?
Your schema should be prescriptive, not descriptive. It tells Claude Code exactly what to do when new data arrives. The Ingestion Workflow section is the most important part. It defines the step-by-step process Claude Code follows every time a new source document enters the system.
The Health Checks section ensures wiki quality stays high over time by catching stale pages, orphaned content, and missing measurements.
CLAUDE.md content looks different from expected?
Claude Code generates content based on your prompt, so its output may vary slightly in wording. The important thing is that all seven sections are present and the logic is correct. You can refine it by asking Claude Code to update specific sections.
Help me review whether my CLAUDE.md schema has all the right sections and logic for a marketing wiki.
Create the content catalog and action log
Two files complete the wiki foundation. The index.md acts as a catalog of every page in your wiki, organized by category. The log.md is an append-only record of every action Claude Code takes on the wiki.
- In your Claude Code session, ask it to create the wiki index file:
Create wiki/index.md with YAML frontmatter (title: Marketing Operations Wiki Index, last_updated: today's date, total_pages: 0). Include sections for Strategic Fronts, KPIs, Individual Contributors, Campaigns, Decisions, and Experiments. Each section should have a comment showing the format for entries that will be added later.
- Approve the file creation when Claude Code proposes it.
What is the index for?
Every time Claude Code ingests a new source or creates a wiki page, it updates index.md with the new entry. This gives both you and Claude Code a quick overview of everything in the knowledge base without scanning every file.
- Now create the action log by giving Claude Code this instruction:
Create wiki/log.md with YAML frontmatter (title: Wiki Action Log, type: append-only). Add a comment showing the entry format: [YYYY-MM-DD HH:MM] ACTION: Description | Pages touched: [list]. Include an example entry as a comment.
- Approve the file creation.
Why an append-only log?
The log creates an audit trail. You can always trace back when a piece of knowledge entered the system, what source it came from, and which pages it touched. This is essential for debugging incorrect information in the wiki later.
- Verify both files exist by checking wiki/index.md and wiki/log.md in your file explorer or by running ls wiki/ in your terminal.
You should see index.md and log.md listed alongside the subdirectories (strategy/, kpis/, ics/, etc.) inside the wiki/ folder.
Files ended up in the wrong directory?
If index.md or log.md landed in the repo root instead of wiki/, ask Claude Code to move them:
Help me fix the location of my wiki index and log files.
- Now commit and push all your new files to GitHub. In Claude Code, ask it to commit and push:
Commit all new files with the message "Add wiki file architecture: directories, CLAUDE.md schema, index, and log" and push to origin.
- Approve the git commands when Claude Code proposes them.
Your wiki architecture is now live in GitHub and will sync to Lovable automatically via the two-way connection.
Push failed or permission denied?
Make sure your local repo has the correct remote URL configured. Run git remote -v to check. If you cloned via HTTPS, you may need to authenticate with a personal access token.
Help me fix a git push failure for my marketing operations wiki repo.
✔️ Awesome, I've got everything!
Great. Double check you've committed and pushed all files to GitHub before moving on.
ⓧ I'd like to double check the full code
Here's what each key file should contain. Compare your versions against these references.
CLAUDE.md (repo root):
# Marketing Operations Wiki Schema
## Purpose
This schema defines how Claude Code maintains the marketing operations wiki for [TEAM NAME]. Follow these conventions for all ingestion, querying, and maintenance operations.
## Architecture
- `raw/` - Source documents (IC reports, meeting notes, strategy docs). Immutable once ingested.
- `wiki/` - LLM-compiled knowledge base. Do not edit manually.
- `wiki/strategy/` - Strategic front pages
- `wiki/kpis/` - KPI definition and tracking pages
- `wiki/ics/` - Individual contributor profiles and performance
- `wiki/campaigns/` - Campaign pages
- `wiki/decisions/` - Decision log compiled pages
- `wiki/experiments/` - Experiment registry pages
- `wiki/index.md` - Content catalog (update on every ingest)
- `wiki/log.md` - Append-only action record
- `output/` - Generated reports, slides, charts
- `templates/` - Document templates for IC reporting and data intake
- `_meta/` - Compile state, health check results, config
## Naming Conventions
- Files: `kebab-case.md` (e.g., `q3-email-campaign.md`)
- Frontmatter slugs: same as filename without extension
- Dates: ISO 8601 (YYYY-MM-DD)
- KPI IDs: `KPI-[FRONT_ABBREV]-[NUMBER]` (e.g., `KPI-PERF-001`)
## Ingestion Workflow
When a new source is added to `raw/`:
1. Read the source document completely
2. Identify which wiki pages are relevant (check index.md)
3. Update each relevant page, preserving existing content
4. If the source introduces a new concept/entity, create a new page
5. Update `wiki/index.md` with any new pages
6. Append entry to `wiki/log.md` with timestamp and summary
7. Flag any contradictions with existing wiki content
## IC Report Ingestion
When an IC report (.md with YAML frontmatter) arrives:
1. Parse frontmatter for: ic_name, week, strategic_front, kpi_references
2. Update `wiki/ics/[ic-name].md` with latest accomplishments and metrics
3. Update relevant `wiki/kpis/[kpi-id].md` pages with new measurements
4. Update `wiki/strategy/[strategic-front].md` with progress notes
5. If blockers are mentioned, flag in the relevant campaign/strategy page
6. Log the ingestion in `wiki/log.md`
## Health Checks
Run periodically to maintain wiki quality:
- Check for contradictions between pages
- Identify stale pages (no updates in 30+ days for active fronts)
- Find orphaned pages (no incoming links)
- Verify all KPI pages have recent measurements
- Ensure all IC pages reflect their latest report
## Cross-Reference Rules
- Use `[[slug]]` notation for internal wiki links
- Every strategic front page must link to its KPIs
- Every IC page must link to their strategic front(s)
- Every decision must link to the context that informed it
- Every experiment must link to the hypothesis and its KPI
## Output Formats
- Default: Markdown pages
- Summaries: Bullet-point markdown
- Comparisons: Markdown tables
- Status reports: Structured markdown with sections
wiki/index.md:
---
title: Marketing Operations Wiki Index
last_updated: 2026-06-06
total_pages: 0
---
# Marketing Operations Wiki Index
## Strategic Fronts
<!-- One entry per strategic front, added during initial setup -->
<!-- Format: - [Front Name](strategy/slug.md) - One-line description -->
## KPIs
<!-- Format: - [KPI-ID: Name](kpis/slug.md) - Owner, Target, Frequency -->
## Individual Contributors
<!-- Format: - [IC Name](ics/slug.md) - Role, Strategic Front(s) -->
## Campaigns
<!-- Format: - [Campaign Name](campaigns/slug.md) - Status, Strategic Front -->
## Decisions
<!-- Format: - [Decision Title](decisions/slug.md) - Date, Decision Maker -->
## Experiments
<!-- Format: - [Experiment Title](experiments/slug.md) - Status, Hypothesis -->
wiki/log.md:
---
title: Wiki Action Log
type: append-only
---
# Wiki Action Log
<!-- Format: [YYYY-MM-DD HH:MM] ACTION: Description | Pages touched: [list] -->
<!-- Example: [2026-06-06 14:30] INGEST: IC weekly report from Maria | Pages touched: ics/maria.md, kpis/KPI-PERF-001.md, strategy/performance-marketing.md -->
Your wiki architecture is locked in. Next up, you'll create the document templates that define how data flows into this system.
Create Document Templates
Your wiki architecture defines how knowledge flows through the system. Now you need to define what that knowledge looks like when it arrives.
Document templates with YAML frontmatter give every piece of incoming data a consistent, machine-parseable structure. When an IC submits a report or you log a decision, the template ensures that Claude Code can automatically route that information to the correct wiki pages.
In this step, get ready to:
- Create the IC Weekly Report template with YAML frontmatter and structured sections.
- Create the Decision Log and Experiment Registry templates for governance tracking.
- Create the Strategic Front and KPI Definition templates for measurement.
Create the IC Weekly Report template
The IC Weekly Report is the most frequently used template in your system. Every individual contributor submits one each week, and the CLAUDE.md ingestion workflow parses its frontmatter to route data across the wiki.
- In Claude Code, instruct it to create templates/ic-weekly-report.md with the following content:
---
template_version: "1.0"
ic_name: ""
week: "YYYY-WXX"
strategic_front: ""
kpi_references: []
submitted_at: ""
source_type: "manual"
---
# Weekly Report - [IC Name] - [Week]
## Accomplishments
<!-- List key deliverables and completed work -->
-
## Metrics
<!-- Report on KPIs you own or contribute to -->
| KPI ID | Current Value | Target | Status |
|--------|--------------|--------|--------|
| | | | |
## Blockers
<!-- Any blockers preventing progress -->
-
## Next Actions
<!-- Planned work for next week -->
-
## Notes
<!-- Additional context, learnings, or flags for management -->
What does this template do?
The YAML frontmatter at the top is what makes this template machine-readable. Fields like ic_name, strategic_front, and kpi_references map directly to what your CLAUDE.md schema expects during the IC Report Ingestion workflow.
The source_type field defaults to manual but changes to slack_slash_command when reports arrive via Slack. This is the source abstraction pattern in action.
Why include template_version?
Every template carries a template_version field in its frontmatter. When you evolve a template in the future (adding new sections, renaming fields), old reports remain parseable because the system knows which version format they used.
This is an inexpensive decision now that prevents expensive migration headaches later.
- Confirm Claude Code created the file by checking that templates/ic-weekly-report.md exists in your repo.
- Open the file and verify it has the YAML frontmatter block between the --- delimiters at the top, followed by the five markdown sections.
Claude Code generated different content?
Claude Code may add extra fields or rearrange sections. Compare your output to the template above and ask Claude Code to adjust any differences. The exact field names in the frontmatter matter because the ingestion workflow references them.
Help me match my IC report template to the exact specification.
Create the Decision Log and Experiment templates
Decisions and experiments are the governance backbone of your marketing operations. Every strategic choice gets logged with its rationale, and every test gets registered with a hypothesis before it runs.
- Instruct Claude Code to create templates/decision-log.md with the following content:
---
template_version: "1.0"
decision_id: ""
date: "YYYY-MM-DD"
decision_maker: ""
strategic_front: ""
status: "active"
review_date: "YYYY-MM-DD"
---
# Decision: [Title]
## Context
<!-- What situation prompted this decision? -->
## Options Considered
<!-- List options that were evaluated -->
1.
2.
3.
## Decision Made
<!-- What was decided and why -->
## Expected Outcome
<!-- What measurable result do we expect? -->
## Data Informing Decision
<!-- What data, reports, or analysis informed this? -->
## Review Criteria
<!-- When and how will we evaluate if this was the right call? -->
What does this template do?
The review_date field is critical. It forces every decision to have a built-in checkpoint where your team evaluates whether the expected outcome actually happened. The status field tracks whether a decision is still active, has been superseded, or reversed.
- Now instruct Claude Code to create templates/experiment.md with the following content:
---
template_version: "1.0"
experiment_id: ""
date_created: "YYYY-MM-DD"
owner: ""
strategic_front: ""
status: "hypothesis"
kpi_references: []
---
# Experiment: [Title]
## Hypothesis
<!-- If we [action], then [expected result], because [reasoning] -->
## Test Design
<!-- How will we test this hypothesis? -->
## Success Criteria
<!-- What specific measurable outcome = success? -->
## Duration
<!-- How long will the test run? -->
## Result
<!-- Fill after experiment completes -->
## Learning
<!-- What did we learn? How does this inform future decisions? -->
What does this template do?
The experiment template enforces scientific rigor. The status field moves through a lifecycle: hypothesis, designing, running, analyzing, completed, or abandoned.
The kpi_references array links each experiment to the KPIs it aims to move. This cross-reference is what your CLAUDE.md schema uses to keep wiki pages connected.
- Verify both files exist by asking Claude Code to list the contents of templates/.
- You should see ic-weekly-report.md, decision-log.md, and experiment.md listed.
Missing a file?
If Claude Code only created one of the two files, ask it specifically to create the missing one. Provide the filename and content from above.
Help me create my missing template file.
Create the Strategic Front and KPI Definition templates
Strategic fronts are the highest-level organizational unit in your system. Each front groups related KPIs, ICs, and campaigns under a single objective. The KPI Definition template captures exactly how each metric is measured and where its data comes from.
- Instruct Claude Code to create templates/strategic-front.md with the following content:
---
template_version: "1.0"
front_id: ""
name: ""
owner: ""
status: "active"
kpi_ids: []
ic_ids: []
created_date: "YYYY-MM-DD"
---
# Strategic Front: [Name]
## Objective
<!-- What is this front trying to achieve? -->
## KPIs
<!-- List KPIs that measure success for this front -->
| KPI ID | Name | Target | Current | Status |
|--------|------|--------|---------|--------|
| | | | | |
## Team
<!-- ICs assigned to this front -->
-
## Current Status
<!-- High-level status summary -->
## Decision Log
<!-- Links to decisions made for this front -->
-
## Active Experiments
<!-- Links to experiments running under this front -->
-
What does this template do?
The kpi_ids and ic_ids arrays in the frontmatter are what make cross-referencing work. When Claude Code ingests a new IC report, it checks these arrays to know which strategic front pages to update.
- Now instruct Claude Code to create templates/kpi-definition.md with the following content:
---
template_version: "1.0"
kpi_id: ""
name: ""
owner: ""
strategic_front: ""
target_value: ""
measurement_frequency: "weekly"
data_source: ""
source_type: ""
formula: ""
created_date: "YYYY-MM-DD"
---
# KPI: [Name]
## Definition
<!-- What does this KPI measure and why does it matter? -->
## Formula
<!-- How is this calculated? -->
## Target
<!-- What is the target value and timeframe? -->
## Measurement
- **Frequency**: [weekly/daily/monthly]
- **Source**: [Where does the data come from?]
- **Owner**: [Who is responsible for reporting?]
## Historical Readings
<!-- Updated by system from kpi_measurements table -->
| Date | Value | Source | Notes |
|------|-------|--------|-------|
| | | | |
## Trend Analysis
<!-- Auto-generated from time-series data -->
What is source abstraction?
The data_source and source_type fields implement the source abstraction pattern. Every KPI records where its data comes from (manual entry, Google Ads, Meta Ads, etc.) without hardcoding any specific integration.
When you add media buying platform integrations in the future, they slot into this same field. No schema changes required.
- Ask Claude Code to list all files in templates/ to confirm you now have all five templates.
- You should see: ic-weekly-report.md, decision-log.md, experiment.md, strategic-front.md, and kpi-definition.md.
- Commit and push all templates to GitHub by asking Claude Code to commit with a message like "Add document templates for IC reporting, decisions, experiments, strategic fronts, and KPIs".
Frontmatter fields don't match?
The exact field names matter because your database schema (coming in the next step) maps directly to these frontmatter keys. If Claude Code used different names (e.g., kpi_id vs kpiId), ask it to rename the fields to match the templates shown above.
Help me fix my template frontmatter fields.
✔️ Awesome, I've got everything!
Great. Make sure all five templates are committed and pushed to GitHub before moving on.
ⓧ I'd like to double check the full code
Here are all five templates for comparison. Open each file and verify the content matches.
---
template_version: "1.0"
ic_name: ""
week: "YYYY-WXX"
strategic_front: ""
kpi_references: []
submitted_at: ""
source_type: "manual"
---
# Weekly Report - [IC Name] - [Week]
## Accomplishments
<!-- List key deliverables and completed work -->
-
## Metrics
<!-- Report on KPIs you own or contribute to -->
| KPI ID | Current Value | Target | Status |
|--------|--------------|--------|--------|
| | | | |
## Blockers
<!-- Any blockers preventing progress -->
-
## Next Actions
<!-- Planned work for next week -->
-
## Notes
<!-- Additional context, learnings, or flags for management -->
---
template_version: "1.0"
decision_id: ""
date: "YYYY-MM-DD"
decision_maker: ""
strategic_front: ""
status: "active"
review_date: "YYYY-MM-DD"
---
# Decision: [Title]
## Context
<!-- What situation prompted this decision? -->
## Options Considered
<!-- List options that were evaluated -->
1.
2.
3.
## Decision Made
<!-- What was decided and why -->
## Expected Outcome
<!-- What measurable result do we expect? -->
## Data Informing Decision
<!-- What data, reports, or analysis informed this? -->
## Review Criteria
<!-- When and how will we evaluate if this was the right call? -->
---
template_version: "1.0"
experiment_id: ""
date_created: "YYYY-MM-DD"
owner: ""
strategic_front: ""
status: "hypothesis"
kpi_references: []
---
# Experiment: [Title]
## Hypothesis
<!-- If we [action], then [expected result], because [reasoning] -->
## Test Design
<!-- How will we test this hypothesis? -->
## Success Criteria
<!-- What specific measurable outcome = success? -->
## Duration
<!-- How long will the test run? -->
## Result
<!-- Fill after experiment completes -->
## Learning
<!-- What did we learn? How does this inform future decisions? -->
---
template_version: "1.0"
front_id: ""
name: ""
owner: ""
status: "active"
kpi_ids: []
ic_ids: []
created_date: "YYYY-MM-DD"
---
# Strategic Front: [Name]
## Objective
<!-- What is this front trying to achieve? -->
## KPIs
<!-- List KPIs that measure success for this front -->
| KPI ID | Name | Target | Current | Status |
|--------|------|--------|---------|--------|
| | | | | |
## Team
<!-- ICs assigned to this front -->
-
## Current Status
<!-- High-level status summary -->
## Decision Log
<!-- Links to decisions made for this front -->
-
## Active Experiments
<!-- Links to experiments running under this front -->
-
---
template_version: "1.0"
kpi_id: ""
name: ""
owner: ""
strategic_front: ""
target_value: ""
measurement_frequency: "weekly"
data_source: ""
source_type: ""
formula: ""
created_date: "YYYY-MM-DD"
---
# KPI: [Name]
## Definition
<!-- What does this KPI measure and why does it matter? -->
## Formula
<!-- How is this calculated? -->
## Target
<!-- What is the target value and timeframe? -->
## Measurement
- **Frequency**: [weekly/daily/monthly]
- **Source**: [Where does the data come from?]
- **Owner**: [Who is responsible for reporting?]
## Historical Readings
<!-- Updated by system from kpi_measurements table -->
| Date | Value | Source | Notes |
|------|-------|--------|-------|
| | | | |
## Trend Analysis
<!-- Auto-generated from time-series data -->
Your templates are locked in. You now have a complete file architecture: the wiki structure for compiled knowledge, the schema that governs how Claude Code maintains it, and the templates that define how raw data enters the system. Next up, you will build the database that stores this data at scale and makes it queryable from a live dashboard.
Deploy the Supabase Database Schema
Your wiki architecture and document templates define how knowledge enters the system. Now you need a production database that stores the structured data those templates generate.
In this step, you will deploy a full PostgreSQL schema to your Supabase project using the SQL Editor. The schema includes time-series KPI tracking, decision logging, source abstraction, and role-based access control from day one.
In this step, get ready to:
- Deploy the core tables (profiles, strategic fronts, KPIs, KPI measurements, ICs) via Supabase SQL Editor.
- Deploy the governance and tracking tables (decision log, experiments, reports, creative assets).
- Configure Row Level Security policies for role-based access control.
Deploy the core tables
The core tables form the backbone of your operations platform. They define who is on your team, what strategic fronts you are pursuing, and how you measure progress over time.
- In your Supabase dashboard, click SQL Editor in the left sidebar.
- Click New Query to open a blank editor.
- Paste the following SQL to enable the UUID extension and create the core tables:
-- Enable necessary extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Profiles table (extends Supabase auth.users)
CREATE TABLE public.profiles (
id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY,
full_name TEXT NOT NULL,
role TEXT NOT NULL CHECK (role IN ('manager', 'ic', 'leadership')),
email TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Strategic Fronts
CREATE TABLE public.strategic_fronts (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
front_id TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
owner_id UUID REFERENCES public.profiles(id),
objective TEXT,
status TEXT DEFAULT 'active' CHECK (status IN ('active', 'paused', 'completed', 'archived')),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- KPI Definitions
CREATE TABLE public.kpis (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
kpi_id TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
owner_id UUID REFERENCES public.profiles(id),
strategic_front_id UUID REFERENCES public.strategic_fronts(id),
target_value NUMERIC,
measurement_frequency TEXT DEFAULT 'weekly' CHECK (measurement_frequency IN ('daily', 'weekly', 'biweekly', 'monthly', 'quarterly')),
data_source TEXT,
source_type TEXT,
formula TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- KPI Measurements (time-series)
CREATE TABLE public.kpi_measurements (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
kpi_id UUID REFERENCES public.kpis(id) ON DELETE CASCADE NOT NULL,
value NUMERIC NOT NULL,
measured_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
source_type TEXT NOT NULL DEFAULT 'manual',
source_reference TEXT,
notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Create index for time-series queries
CREATE INDEX idx_kpi_measurements_kpi_date ON public.kpi_measurements(kpi_id, measured_at DESC);
-- Individual Contributors
CREATE TABLE public.ics (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
profile_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE UNIQUE NOT NULL,
strategic_front_id UUID REFERENCES public.strategic_fronts(id),
role_title TEXT,
start_date DATE,
status TEXT DEFAULT 'active' CHECK (status IN ('active', 'inactive')),
created_at TIMESTAMPTZ DEFAULT NOW()
);
What does this SQL do?
- profiles extends Supabase's built-in auth.users table with a role field. This is the foundation for RBAC (Role-Based Access Control). Every user is a manager, IC, or leadership.
- strategic_fronts represents the major business areas your team is working on (e.g. Performance Marketing, Content, Brand).
- kpis stores the definition of each metric. The source_type field is the source abstraction layer. Future integrations (Google Ads, Meta) slot in without schema changes.
- kpi_measurements is the time-series table. Every reading gets a timestamp and source reference, so you can track trends over weeks and months.
- The descending index on (kpi_id, measured_at DESC) makes "get the latest N readings for this KPI" queries fast.
- Click Run (or press Cmd+Enter on macOS / Ctrl+Enter on Windows).
- You should see a "Success" message at the bottom of the editor confirming the tables were created.
Seeing an error about uuid-ossp?
If you see an error mentioning the uuid-ossp extension, it may already be enabled on your project. You can safely remove the CREATE EXTENSION line and re-run.
If you see an error about auth.users not existing, make sure you are running the SQL in the correct Supabase project (the one you created in Step 1).
Still stuck? Help me debug my Supabase SQL schema deployment error.
Deploy the governance and tracking tables
The governance tables capture the qualitative side of your operations. They record decisions, experiments, IC report submissions, and creative asset metadata from Google Drive.
- In the same SQL Editor query (or click New Query for a fresh tab), paste the following SQL:
-- Decision Log
CREATE TABLE public.decision_log (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
decision_id TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
decision_maker_id UUID REFERENCES public.profiles(id),
strategic_front_id UUID REFERENCES public.strategic_fronts(id),
context TEXT,
options_considered JSONB,
decision_made TEXT,
expected_outcome TEXT,
data_informing TEXT,
status TEXT DEFAULT 'active' CHECK (status IN ('active', 'superseded', 'reversed', 'under_review')),
decision_date DATE NOT NULL DEFAULT CURRENT_DATE,
review_date DATE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Experiments
CREATE TABLE public.experiments (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
experiment_id TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
owner_id UUID REFERENCES public.profiles(id),
strategic_front_id UUID REFERENCES public.strategic_fronts(id),
hypothesis TEXT,
test_design TEXT,
success_criteria TEXT,
duration TEXT,
status TEXT DEFAULT 'hypothesis' CHECK (status IN ('hypothesis', 'designing', 'running', 'analyzing', 'completed', 'abandoned')),
result TEXT,
learning TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Reports (IC submissions)
CREATE TABLE public.reports (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
ic_id UUID REFERENCES public.ics(id),
content TEXT NOT NULL,
template_version TEXT DEFAULT '1.0',
source_type TEXT NOT NULL DEFAULT 'manual' CHECK (source_type IN ('manual', 'slack_slash_command', 'file_upload', 'api')),
source_reference TEXT,
week TEXT,
strategic_front_id UUID REFERENCES public.strategic_fronts(id),
parsed_frontmatter JSONB,
submitted_at TIMESTAMPTZ DEFAULT NOW()
);
-- Creative Assets (Google Drive metadata only)
CREATE TABLE public.creative_assets (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
google_drive_file_id TEXT NOT NULL,
name TEXT NOT NULL,
file_type TEXT CHECK (file_type IN ('video', 'image', 'logo', 'document', 'presentation', 'other')),
thumbnail_url TEXT,
google_drive_url TEXT NOT NULL,
associated_campaign TEXT,
associated_strategic_front_id UUID REFERENCES public.strategic_fronts(id),
uploaded_by UUID REFERENCES public.profiles(id),
tags TEXT[],
created_at TIMESTAMPTZ DEFAULT NOW()
);
What does this SQL do?
- decision_log stores structured decisions with a review_date field. This lets you revisit decisions on a schedule and evaluate whether they played out as expected.
- experiments tracks the hypothesis-to-learning lifecycle. Each experiment links to a strategic front and carries its own status progression.
- reports receives IC submissions from multiple sources. The source_type field distinguishes manual uploads from Slack slash commands or API ingestion. The template_version field ensures old reports stay parseable when templates evolve.
- creative_assets stores only metadata. The actual video and image files remain in Google Drive. This keeps your database lean and avoids hitting the 500 MB free-tier limit with binary files.
- Click Run to execute the SQL.
- You should see another "Success" confirmation. You can verify the tables exist by clicking Table Editor in the left sidebar. All 9 tables should appear in the list.
Why store only metadata for creative assets?
Binary files (videos, images, logos) are large and expensive to query. Storing them in Supabase would consume your 500 MB database allowance quickly and give you no benefit. Your team already uses Google Drive, so keeping files there and referencing them by ID gives you the best of both worlds. The dashboard links out to Drive for viewing.
Tables not appearing in Table Editor?
Make sure you ran Part 1 first. The governance tables have foreign key references to profiles, ics, and strategic_fronts. If those tables do not exist yet, the SQL will fail.
Need help? Help me figure out why my governance tables SQL is failing in Supabase.
Configure Row Level Security policies
Row Level Security (RLS) controls which rows each authenticated user can read or write. Without RLS, any authenticated user could access every row in every table. With RLS enabled, the database itself enforces access rules.
- Open a new query in the SQL Editor by clicking New Query.
- Paste the following SQL to enable RLS on all tables and create the access policies:
-- Enable Row Level Security on all tables
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.strategic_fronts ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.kpis ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.kpi_measurements ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ics ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.decision_log ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.experiments ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.reports ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.creative_assets ENABLE ROW LEVEL SECURITY;
-- RLS Policies
CREATE POLICY "Profiles are viewable by all authenticated users"
ON public.profiles FOR SELECT TO authenticated USING (true);
CREATE POLICY "Users can update own profile"
ON public.profiles FOR UPDATE TO authenticated USING (auth.uid() = id);
CREATE POLICY "Strategic fronts viewable by all authenticated"
ON public.strategic_fronts FOR SELECT TO authenticated USING (true);
CREATE POLICY "Managers can insert strategic fronts"
ON public.strategic_fronts FOR INSERT TO authenticated
WITH CHECK (EXISTS (SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role = 'manager'));
CREATE POLICY "KPIs viewable by all authenticated"
ON public.kpis FOR SELECT TO authenticated USING (true);
CREATE POLICY "Managers can manage KPIs"
ON public.kpis FOR ALL TO authenticated
USING (EXISTS (SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role IN ('manager', 'leadership')));
CREATE POLICY "KPI measurements viewable by all authenticated"
ON public.kpi_measurements FOR SELECT TO authenticated USING (true);
CREATE POLICY "Authenticated users can insert measurements"
ON public.kpi_measurements FOR INSERT TO authenticated WITH CHECK (true);
CREATE POLICY "ICs can view own reports"
ON public.reports FOR SELECT TO authenticated
USING (ic_id IN (SELECT id FROM public.ics WHERE profile_id = auth.uid())
OR EXISTS (SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role IN ('manager', 'leadership')));
CREATE POLICY "Authenticated users can insert reports"
ON public.reports FOR INSERT TO authenticated WITH CHECK (true);
CREATE POLICY "Creative assets viewable by all authenticated"
ON public.creative_assets FOR SELECT TO authenticated USING (true);
CREATE POLICY "Authenticated users can register creative assets"
ON public.creative_assets FOR INSERT TO authenticated WITH CHECK (true);
CREATE POLICY "Decision log viewable by all authenticated"
ON public.decision_log FOR SELECT TO authenticated USING (true);
CREATE POLICY "Managers can manage decisions"
ON public.decision_log FOR INSERT TO authenticated
WITH CHECK (EXISTS (SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role IN ('manager', 'leadership')));
CREATE POLICY "Experiments viewable by all authenticated"
ON public.experiments FOR SELECT TO authenticated USING (true);
CREATE POLICY "Authenticated users can manage experiments"
ON public.experiments FOR ALL TO authenticated USING (true);
How do these RLS policies work?
Every policy uses TO authenticated to scope access to signed-in users only. Anonymous visitors see nothing.
The access model follows a clear hierarchy. Everyone can read most data (strategic fronts, KPIs, experiments). Only managers and leadership can create strategic fronts, KPIs, and decisions. ICs can only see their own reports, while managers see all reports.
The auth.uid() function is built into Supabase. It returns the UUID of the currently authenticated user, which matches the id column in the profiles table.
- Click Run to execute the RLS configuration.
- You should see a "Success" confirmation. To verify RLS is active, click Authentication in the left sidebar, then click Policies. You should see all your policies listed under each table.
Seeing a 'policy already exists' error?
If you ran this SQL more than once, the policies already exist from the first run. You can safely ignore this error. Your RLS configuration is already in place.
Need help? Help me troubleshoot my RLS policy deployment in Supabase.
Now save the complete schema SQL to your local repository so it lives alongside your wiki architecture.
- Switch back to your terminal in the project repo.
- Create the supabase directory and schema file by running these commands:
mkdir -p supabase
touch supabase/schema.sql
- Open supabase/schema.sql in your editor and paste the complete SQL (all three parts combined into a single file).
- Save the file, then commit and push to GitHub by running:
git add supabase/schema.sql
git commit -m "Add complete database schema with RLS policies"
git push
- Confirm the push succeeded by checking your GitHub repository. You should see the supabase/schema.sql file in the repo.
Why save the schema in the repo?
Your GitHub repo is the single source of truth for this project. Keeping the schema file versioned means Claude Code can reference it, Lovable can see your database structure, and you have a record of every change you make to the data model over time.
✔️ Awesome, I've got everything!
Great. Double check you have saved supabase/schema.sql and pushed it to GitHub.
ⓧ I'd like to double check the full code
Here is the complete supabase/schema.sql file with all three parts combined:
-- Enable necessary extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Profiles table (extends Supabase auth.users)
CREATE TABLE public.profiles (
id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY,
full_name TEXT NOT NULL,
role TEXT NOT NULL CHECK (role IN ('manager', 'ic', 'leadership')),
email TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Strategic Fronts
CREATE TABLE public.strategic_fronts (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
front_id TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
owner_id UUID REFERENCES public.profiles(id),
objective TEXT,
status TEXT DEFAULT 'active' CHECK (status IN ('active', 'paused', 'completed', 'archived')),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- KPI Definitions
CREATE TABLE public.kpis (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
kpi_id TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
owner_id UUID REFERENCES public.profiles(id),
strategic_front_id UUID REFERENCES public.strategic_fronts(id),
target_value NUMERIC,
measurement_frequency TEXT DEFAULT 'weekly' CHECK (measurement_frequency IN ('daily', 'weekly', 'biweekly', 'monthly', 'quarterly')),
data_source TEXT,
source_type TEXT,
formula TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- KPI Measurements (time-series)
CREATE TABLE public.kpi_measurements (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
kpi_id UUID REFERENCES public.kpis(id) ON DELETE CASCADE NOT NULL,
value NUMERIC NOT NULL,
measured_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
source_type TEXT NOT NULL DEFAULT 'manual',
source_reference TEXT,
notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_kpi_measurements_kpi_date ON public.kpi_measurements(kpi_id, measured_at DESC);
-- Individual Contributors
CREATE TABLE public.ics (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
profile_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE UNIQUE NOT NULL,
strategic_front_id UUID REFERENCES public.strategic_fronts(id),
role_title TEXT,
start_date DATE,
status TEXT DEFAULT 'active' CHECK (status IN ('active', 'inactive')),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Decision Log
CREATE TABLE public.decision_log (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
decision_id TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
decision_maker_id UUID REFERENCES public.profiles(id),
strategic_front_id UUID REFERENCES public.strategic_fronts(id),
context TEXT,
options_considered JSONB,
decision_made TEXT,
expected_outcome TEXT,
data_informing TEXT,
status TEXT DEFAULT 'active' CHECK (status IN ('active', 'superseded', 'reversed', 'under_review')),
decision_date DATE NOT NULL DEFAULT CURRENT_DATE,
review_date DATE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Experiments
CREATE TABLE public.experiments (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
experiment_id TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
owner_id UUID REFERENCES public.profiles(id),
strategic_front_id UUID REFERENCES public.strategic_fronts(id),
hypothesis TEXT,
test_design TEXT,
success_criteria TEXT,
duration TEXT,
status TEXT DEFAULT 'hypothesis' CHECK (status IN ('hypothesis', 'designing', 'running', 'analyzing', 'completed', 'abandoned')),
result TEXT,
learning TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Reports (IC submissions)
CREATE TABLE public.reports (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
ic_id UUID REFERENCES public.ics(id),
content TEXT NOT NULL,
template_version TEXT DEFAULT '1.0',
source_type TEXT NOT NULL DEFAULT 'manual' CHECK (source_type IN ('manual', 'slack_slash_command', 'file_upload', 'api')),
source_reference TEXT,
week TEXT,
strategic_front_id UUID REFERENCES public.strategic_fronts(id),
parsed_frontmatter JSONB,
submitted_at TIMESTAMPTZ DEFAULT NOW()
);
-- Creative Assets (Google Drive metadata only)
CREATE TABLE public.creative_assets (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
google_drive_file_id TEXT NOT NULL,
name TEXT NOT NULL,
file_type TEXT CHECK (file_type IN ('video', 'image', 'logo', 'document', 'presentation', 'other')),
thumbnail_url TEXT,
google_drive_url TEXT NOT NULL,
associated_campaign TEXT,
associated_strategic_front_id UUID REFERENCES public.strategic_fronts(id),
uploaded_by UUID REFERENCES public.profiles(id),
tags TEXT[],
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Enable Row Level Security on all tables
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.strategic_fronts ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.kpis ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.kpi_measurements ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ics ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.decision_log ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.experiments ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.reports ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.creative_assets ENABLE ROW LEVEL SECURITY;
-- RLS Policies
CREATE POLICY "Profiles are viewable by all authenticated users"
ON public.profiles FOR SELECT TO authenticated USING (true);
CREATE POLICY "Users can update own profile"
ON public.profiles FOR UPDATE TO authenticated USING (auth.uid() = id);
CREATE POLICY "Strategic fronts viewable by all authenticated"
ON public.strategic_fronts FOR SELECT TO authenticated USING (true);
CREATE POLICY "Managers can insert strategic fronts"
ON public.strategic_fronts FOR INSERT TO authenticated
WITH CHECK (EXISTS (SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role = 'manager'));
CREATE POLICY "KPIs viewable by all authenticated"
ON public.kpis FOR SELECT TO authenticated USING (true);
CREATE POLICY "Managers can manage KPIs"
ON public.kpis FOR ALL TO authenticated
USING (EXISTS (SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role IN ('manager', 'leadership')));
CREATE POLICY "KPI measurements viewable by all authenticated"
ON public.kpi_measurements FOR SELECT TO authenticated USING (true);
CREATE POLICY "Authenticated users can insert measurements"
ON public.kpi_measurements FOR INSERT TO authenticated WITH CHECK (true);
CREATE POLICY "ICs can view own reports"
ON public.reports FOR SELECT TO authenticated
USING (ic_id IN (SELECT id FROM public.ics WHERE profile_id = auth.uid())
OR EXISTS (SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role IN ('manager', 'leadership')));
CREATE POLICY "Authenticated users can insert reports"
ON public.reports FOR INSERT TO authenticated WITH CHECK (true);
CREATE POLICY "Creative assets viewable by all authenticated"
ON public.creative_assets FOR SELECT TO authenticated USING (true);
CREATE POLICY "Authenticated users can register creative assets"
ON public.creative_assets FOR INSERT TO authenticated WITH CHECK (true);
CREATE POLICY "Decision log viewable by all authenticated"
ON public.decision_log FOR SELECT TO authenticated USING (true);
CREATE POLICY "Managers can manage decisions"
ON public.decision_log FOR INSERT TO authenticated
WITH CHECK (EXISTS (SELECT 1 FROM public.profiles WHERE id = auth.uid() AND role IN ('manager', 'leadership')));
CREATE POLICY "Experiments viewable by all authenticated"
ON public.experiments FOR SELECT TO authenticated USING (true);
CREATE POLICY "Authenticated users can manage experiments"
ON public.experiments FOR ALL TO authenticated USING (true);
Your database is live and locked down with role-based access control. Next up, you will build the Lovable dashboard that connects to this schema and gives your team a visual interface for KPIs, reports, and creative assets.
Build the Lovable Dashboard
Your database is live and secured with row-level policies. Now you need a way for humans to actually see and interact with that data.
In this step, you'll use Lovable's AI to generate a full web dashboard connected to your Supabase database. You'll prompt Lovable to build pages for KPI tracking, IC management, report uploads, and creative asset browsing.
In this step, get ready to:
- Build the main dashboard layout with sidebar navigation and KPI overview.
- Create the IC registry, report upload page, and IC profile views.
- Build a Creative Assets browser with Google Drive metadata and a registration form.
Prompt the main dashboard layout and KPI overview
Lovable generates full React + Tailwind applications from natural language prompts. The more specific your prompt, the closer the output matches what you need on the first pass.
- Switch back to your Lovable project in your browser.
- In the prompt input area, paste the following prompt to generate your main dashboard layout:
Build a marketing operations dashboard with these pages and features:
1. SIDEBAR NAVIGATION (persistent on all pages):
- KPIs
- ICs (Individual Contributors)
- Reports
- Decisions
- Creative Assets
- A "Strategic Fronts" summary link at the top
2. KPI OVERVIEW PAGE (default/home page):
- Query the `kpis` table joined with `kpi_measurements` from Supabase
- Show each KPI as a card with: name, current value (latest measurement), target value, and a trend indicator (up/down arrow or percentage change based on the last 2 measurements)
- Group KPIs by strategic front
- Color-code: green if current >= target, yellow if 60-99% of target, red if below 60%
3. STRATEGIC FRONTS SUMMARY VIEW:
- Query the `strategic_fronts` table
- Show each front with its name, objective, status badge, and a count of associated KPIs and ICs
- Link each front to a detail view showing its KPIs and team members
Use a clean, professional dark sidebar with light main content area. Connect to my Supabase project for all data.
- Press Enter or click the send button to submit the prompt.
- Wait for Lovable to generate the code. This typically takes 30-60 seconds.
What is Lovable doing with this prompt?
Lovable reads your prompt and generates a full React + Tailwind application. Because you already connected Supabase via Project Settings > Integrations > Supabase, it automatically uses the @supabase/supabase-js client to query your tables.
The generated code includes routing, components, and Supabase queries. You'll iterate on it with follow-up prompts rather than editing code directly.
- Once generation completes, check the live preview in Lovable's right panel.
- You should see a sidebar with navigation links and a KPI overview page as the default view.
- Click through the sidebar links to confirm each page placeholder exists.
Dashboard not showing data from Supabase?
Your tables are likely empty right now, so the KPI cards won't show real values yet. You should see the page structure (sidebar, card layouts, headings) even without data.
If you see connection errors, confirm your Supabase integration is still active in Project Settings > Integrations > Supabase. Help me troubleshoot my Lovable-Supabase connection errors.
Build the IC section and report upload page
Your ICs need a way to appear in the system and submit their weekly reports. This prompt builds three connected views: a registry of all ICs, a report upload form, and individual profile pages.
- In Lovable's prompt input, submit this follow-up prompt to build the IC pages:
Add these pages to the existing dashboard:
1. IC REGISTRY PAGE (linked from sidebar "ICs"):
- Query the `ics` table joined with `profiles` (for full_name) and `strategic_fronts` (for front name)
- Show a table/list of all ICs with columns: Name, Role Title, Strategic Front, Status, Start Date
- Each IC name links to their individual profile page
- Add a button to register a new IC (form with: profile selection, strategic front, role title, start date)
2. REPORT UPLOAD PAGE (linked from sidebar "Reports"):
- A text area where users paste their markdown report content (following the IC weekly report template)
- On submission, parse YAML frontmatter between the opening `---` and closing `---`
- Extract fields: ic_name, week, strategic_front, template_version, kpi_references
- Store in the `reports` table with: content (full text), template_version from frontmatter, source_type = 'file_upload', week from frontmatter, parsed_frontmatter as JSONB
- Show a success message after submission
- Below the form, show a list of recent reports with: submitter name, week, strategic front, submitted date
3. IC PROFILE PAGE (dynamic route for each IC):
- Show the IC's name, role title, strategic front, and start date
- List their submitted reports (from `reports` table filtered by ic_id) with links to view full content
- Show KPI contributions: which KPIs their strategic front owns, with latest values
Use the existing sidebar navigation and design system.
- Wait for Lovable to regenerate the app with the new pages.
Why parse YAML frontmatter on upload?
Your IC weekly report template uses YAML frontmatter (the block between --- delimiters) to carry structured metadata like ic_name, week, and template_version.
Parsing this on upload means the dashboard can filter and sort reports by week, IC, or strategic front without re-reading the full text content each time.
- In the live preview, click ICs in the sidebar. You should see an IC registry table (empty for now) with a registration button.
- Click Reports in the sidebar. You should see a text area for pasting report content and a list of recent submissions below it.
Pages missing from sidebar or not rendering?
Lovable sometimes generates pages without linking them to the existing sidebar. If you see the pages but the sidebar links are broken, submit a follow-up prompt: "Fix the sidebar navigation links to correctly route to the ICs, Reports, and IC profile pages you just created."
If the report upload form appears but doesn't parse frontmatter correctly, you'll test this more thoroughly once you have ICs registered. For now, confirm the form renders.
Help me fix my Lovable-generated dashboard pages that aren't routing correctly.
Build the Creative Assets browser and registration form
Your team's creative assets (videos, images, logos) live in Google Drive. The dashboard only stores metadata about those files. This keeps your database lean while giving the team a browsable catalog with links back to the actual files.
- In Lovable's prompt input, submit this follow-up prompt to build the Creative Assets section:
Add a Creative Assets browser page to the dashboard:
1. CREATIVE ASSETS BROWSER (linked from sidebar "Creative Assets"):
- Query the `creative_assets` table from Supabase
- Display assets in a grid/card layout showing: name, file_type (as a badge/icon), thumbnail_url (as the card image if available), associated_campaign, and tags
- Each card has a "View in Drive" button that opens the google_drive_url in a new tab
- Add filter options: filter by file_type (video, image, logo, document, presentation, other) and by associated_campaign
- Show the associated strategic front name for each asset
2. REGISTER NEW CREATIVE ASSET FORM (accessible from a button on the Creative Assets page):
- Form fields: Name, File Type (dropdown: video, image, logo, document, presentation, other), Google Drive Share Link (text input), Thumbnail URL (optional), Associated Campaign (text input), Associated Strategic Front (dropdown from strategic_fronts table), Tags (comma-separated text input)
- On submit: extract the Google Drive file ID from the share link URL, store in `creative_assets` table with google_drive_file_id, name, file_type, thumbnail_url, google_drive_url (the full link), associated_campaign, associated_strategic_front_id, and tags as a text array
- Show success message after registration
Maintain the existing sidebar and design system.
- Wait for Lovable to generate the Creative Assets pages.
Why store only metadata for creative assets?
Video files and high-resolution images can be hundreds of megabytes. Your Supabase Free Plan gives you 1 GB of storage total. Storing only the metadata (file name, type, Drive link) keeps your database focused on queryable information.
The actual binary files stay in Google Drive where your team already collaborates on them. The dashboard becomes a searchable catalog that links out to the real files.
- Click Creative Assets in the sidebar. You should see a grid layout (empty for now) with filter options and a button to register new assets.
- Click the registration button. You should see a form with fields for name, file type dropdown, Google Drive share link, and optional thumbnail URL.
Form not extracting Google Drive file ID?
If the form stores the full URL but doesn't extract the file ID separately, submit a follow-up prompt: "Update the creative asset registration form to extract the Google Drive file ID from the share URL (the string between /d/ and the next /) and store it in the google_drive_file_id column."
Help me fix my Lovable creative assets form to correctly parse Google Drive share URLs.
Lovable automatically syncs your generated code to GitHub via the two-way sync you configured earlier. Your dashboard code is now committed to your repository.
Iterating with follow-up prompts
Lovable rarely gets everything perfect on the first prompt. Use specific follow-up prompts to fix styling, correct routing, or adjust data queries. Each follow-up builds on the existing codebase rather than regenerating from scratch.
Your dashboard is live and connected to real data. Next up, you'll wire in Slack so your ICs can submit reports without ever opening this dashboard.
Wire Up the Slack Integration
Your dashboard is live and connected to your database. But right now, the only way for ICs to submit reports is through the dashboard's upload page.
Your ICs already live in Slack all day. Forcing them to open a separate dashboard just to submit a weekly report adds friction that kills adoption. In this step, you'll wire up a /report slash command so ICs can submit reports without ever leaving Slack.
In this step, get ready to:
- Create a Slack App with a /report slash command.
- Build and deploy a Supabase Edge Function to process incoming reports.
- Test the full end-to-end flow from Slack to dashboard.
Create a Slack App and configure the slash command
Slack slash commands work by sending an HTTP POST request to a URL you specify whenever someone types the command. You'll create a Slack App, add a /report command to it, and point it at your Supabase Edge Function.
- Open https://api.slack.com/apps in your browser and sign in with your workspace admin account.
- Click Create New App.
- Select From scratch.
- Enter a name for your app (e.g. Marketing Ops Reporter) and select your workspace from the dropdown.
- Click Create App.
You'll land on the app's Basic Information page.
- Scroll down to App Credentials and copy the Signing Secret. Save it somewhere safe for later.
- In the left sidebar, click Slash Commands.
- Click Create New Command.
- In the Command field, enter /report.
- Leave the Request URL blank for now. You'll fill this in after deploying the Edge Function.
- In the Short Description field, enter Submit a weekly report.
- Click Save to create the command.
- In the left sidebar, click Install App.
- Click Install to Workspace.
- Click Allow on the permissions screen.
Why a slash command instead of the Events API?
Slash commands are user-initiated. The IC decides when to submit their report by typing /report followed by their content. This is simpler than monitoring every message in a channel and trying to parse which ones are reports.
For an MVP with 2 ICs, this approach avoids channel monitoring complexity and gives you structured input from day one.
Create and deploy the Supabase Edge Function
Your Edge Function receives the HTTP POST from Slack, extracts the report text, parses any YAML frontmatter, and stores it in the reports table. You'll deploy it directly from the Supabase dashboard.
- Switch back to your Supabase dashboard.
- In the left sidebar, click Edge Functions.
- Click Deploy a new function.
- Select Via Editor.
- Name the function slack-report-intake.
- Replace the starter code with the following:
import { createClient } from "npm:@supabase/supabase-js@2";
const SLACK_SIGNING_SECRET = Deno.env.get("SLACK_SIGNING_SECRET") || "";
const SUPABASE_URL = Deno.env.get("SUPABASE_URL") || "";
const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") || "";
Deno.serve(async (req: Request) => {
// Only accept POST requests from Slack
if (req.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
// Parse the URL-encoded body Slack sends
const body = await req.text();
const params = new URLSearchParams(body);
const command = params.get("command");
const text = params.get("text") || "";
const userId = params.get("user_id") || "";
const userName = params.get("user_name") || "";
// Verify this is the /report command
if (command !== "/report") {
return new Response(
JSON.stringify({ response_type: "ephemeral", text: "Unknown command." }),
{ headers: { "Content-Type": "application/json" } }
);
}
// Require report content
if (!text.trim()) {
return new Response(
JSON.stringify({
response_type: "ephemeral",
text: "Please include your report content after /report. Example:\n`/report ## Accomplishments\n- Launched Q3 campaign\n## Metrics\nCTR: 2.4%`",
}),
{ headers: { "Content-Type": "application/json" } }
);
}
// Create a Supabase client with the service role key
const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY);
// Parse simple YAML frontmatter if present
let parsedFrontmatter: Record<string, string> = {};
let content = text;
if (text.startsWith("---")) {
const endIndex = text.indexOf("---", 3);
if (endIndex !== -1) {
const frontmatterBlock = text.substring(3, endIndex).trim();
content = text.substring(endIndex + 3).trim();
frontmatterBlock.split("\n").forEach((line) => {
const colonIndex = line.indexOf(":");
if (colonIndex !== -1) {
const key = line.substring(0, colonIndex).trim();
const value = line.substring(colonIndex + 1).trim().replace(/^["']|["']$/g, "");
parsedFrontmatter[key] = value;
}
});
}
}
// Insert the report into the reports table
const { error } = await supabase.from("reports").insert({
content: content,
template_version: parsedFrontmatter.template_version || "1.0",
source_type: "slack_slash_command",
source_reference: `slack:${userId}:${userName}`,
week: parsedFrontmatter.week || null,
parsed_frontmatter: parsedFrontmatter,
submitted_at: new Date().toISOString(),
});
if (error) {
console.error("Supabase insert error:", error);
return new Response(
JSON.stringify({
response_type: "ephemeral",
text: "There was an error storing your report. Please try again or contact your manager.",
}),
{ headers: { "Content-Type": "application/json" } }
);
}
// Return success confirmation (only visible to the IC)
return new Response(
JSON.stringify({
response_type: "ephemeral",
text: "Your report has been submitted successfully. It will appear in the dashboard shortly.",
}),
{ headers: { "Content-Type": "application/json" } }
);
});
What does this code do?
- The function only accepts POST requests, which is how Slack delivers slash command payloads.
- It reads the command, text, user_id, and user_name fields from Slack's URL-encoded payload.
- If the report text starts with ---, the function parses YAML frontmatter (extracting fields like template_version and week) and separates it from the body content.
- It inserts a row into the reports table with source_type set to slack_slash_command so you can trace where each report came from.
- The function returns an ephemeral message (only visible to the IC who typed the command) confirming success or reporting an error.
- Click Deploy function at the bottom of the editor.
After deployment completes, Supabase gives you a function URL. It follows this pattern:
https://[[PROJECT_ID="your-supabase-project-id"]].supabase.co/functions/v1/slack-report-intake
- Copy this URL from the Edge Functions page in your Supabase dashboard.
Slack does not send a Supabase JWT with its requests. It uses its own signing secret for verification. You need to disable JWT verification for this function so Slack can reach it.
- In the Edge Functions settings for slack-report-intake, disable Enforce JWT Verification.
Next, add the Slack Signing Secret so your function can verify incoming requests.
- In your Supabase dashboard, navigate to the Edge Function Secrets Management page.
- Add a new secret with the key SLACK_SIGNING_SECRET and paste the Signing Secret you copied earlier from your Slack app.
- Click Save.
What about SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY?
Supabase automatically provisions SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY as predefined secrets for all Edge Functions. You do not need to set them manually.
The service role key bypasses Row Level Security. Your Edge Function needs this because Slack's request does not carry a Supabase user JWT. The function acts as a trusted server-side service that inserts reports on behalf of ICs.
Now go back to Slack and connect the dots.
- Return to your Slack app at https://api.slack.com/apps.
- Click Slash Commands in the left sidebar.
- Click the pencil icon next to your /report command to edit it.
- Paste your Edge Function URL into the Request URL field.
- Click Save.
Slack says the URL is invalid?
Supabase Edge Function URLs always use HTTPS. Make sure you copied the full URL including the https:// prefix. If you accidentally truncated it, go back to the Edge Functions page in Supabase and copy the URL again.
help me fix my Slack slash command Request URL
✔️ Awesome, I've got everything!
Great. Make sure you saved the Edge Function secrets and your slash command has the correct Request URL. Next you'll test the full flow.
ⓧ I'd like to double check the full code
Here is the complete Edge Function code for supabase/functions/slack-report-intake/index.ts:
import { createClient } from "npm:@supabase/supabase-js@2";
const SLACK_SIGNING_SECRET = Deno.env.get("SLACK_SIGNING_SECRET") || "";
const SUPABASE_URL = Deno.env.get("SUPABASE_URL") || "";
const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") || "";
Deno.serve(async (req: Request) => {
if (req.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
const body = await req.text();
const params = new URLSearchParams(body);
const command = params.get("command");
const text = params.get("text") || "";
const userId = params.get("user_id") || "";
const userName = params.get("user_name") || "";
if (command !== "/report") {
return new Response(
JSON.stringify({ response_type: "ephemeral", text: "Unknown command." }),
{ headers: { "Content-Type": "application/json" } }
);
}
if (!text.trim()) {
return new Response(
JSON.stringify({
response_type: "ephemeral",
text: "Please include your report content after /report. Example:\n`/report ## Accomplishments\n- Launched Q3 campaign\n## Metrics\nCTR: 2.4%`",
}),
{ headers: { "Content-Type": "application/json" } }
);
}
const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY);
// Parse simple YAML frontmatter if present
let parsedFrontmatter: Record<string, string> = {};
let content = text;
if (text.startsWith("---")) {
const endIndex = text.indexOf("---", 3);
if (endIndex !== -1) {
const frontmatterBlock = text.substring(3, endIndex).trim();
content = text.substring(endIndex + 3).trim();
frontmatterBlock.split("\n").forEach((line) => {
const colonIndex = line.indexOf(":");
if (colonIndex !== -1) {
const key = line.substring(0, colonIndex).trim();
const value = line.substring(colonIndex + 1).trim().replace(/^["']|["']$/g, "");
parsedFrontmatter[key] = value;
}
});
}
}
const { error } = await supabase.from("reports").insert({
content: content,
template_version: parsedFrontmatter.template_version || "1.0",
source_type: "slack_slash_command",
source_reference: `slack:${userId}:${userName}`,
week: parsedFrontmatter.week || null,
parsed_frontmatter: parsedFrontmatter,
submitted_at: new Date().toISOString(),
});
if (error) {
console.error("Supabase insert error:", error);
return new Response(
JSON.stringify({
response_type: "ephemeral",
text: "There was an error storing your report. Please try again or contact your manager.",
}),
{ headers: { "Content-Type": "application/json" } }
);
}
return new Response(
JSON.stringify({
response_type: "ephemeral",
text: "Your report has been submitted successfully. It will appear in the dashboard shortly.",
}),
{ headers: { "Content-Type": "application/json" } }
);
});
Test the full end-to-end flow
Everything is wired up. Time to prove it works by sending a real report from Slack and watching it appear in both Supabase and your Lovable dashboard.
- Open your Slack workspace.
- In any channel or DM, type the following and press Enter:
/report ## Accomplishments
- Launched the Q3 email campaign
- A/B tested two subject lines
## Metrics
Open rate: 24.5%
CTR: 3.1%
## Blockers
- Waiting on design assets for landing page
- You should see an ephemeral confirmation message that only you can see: "Your report has been submitted successfully. It will appear in the dashboard shortly."
Now verify the data arrived in Supabase.
- Switch back to your Supabase dashboard.
- Navigate to Table Editor and select the reports table.
- You should see a new row with source_type set to slack_slash_command and your report content in the content column.
Finally, confirm the report appears in your Lovable dashboard.
- Open your Lovable dashboard in a browser tab.
- Navigate to the Reports page. Your Slack-submitted report should appear in the list.
- If you have an IC profile set up, check the IC's profile page to confirm the report is linked there too.
Report not showing up?
- Check your Edge Function logs in Supabase > Edge Functions > slack-report-intake > Logs. Look for error messages about missing secrets or database insert failures.
- Verify SLACK_SIGNING_SECRET is set correctly in Edge Function secrets. SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are auto-provisioned, but confirm they appear in your secrets list.
- Make sure JWT verification is disabled for this function. Slack does not send a Supabase JWT.
- Confirm the Request URL in your Slack slash command settings matches the exact Edge Function URL from Supabase.
help me debug why my /report slash command is not storing data in Supabase
What about the YAML frontmatter?
Your Edge Function supports an optional frontmatter block at the start of the report. If an IC includes --- delimiters with key-value pairs, the function extracts those into the parsed_frontmatter JSON column. This means ICs can optionally tag their reports with week, template_version, or any other field from the IC weekly report template.
For now, plain text reports work fine. The frontmatter becomes valuable as your team adopts the template structure.
Your ICs can now submit reports from inside Slack with zero friction. The data flows from a slash command through your Edge Function into Supabase, and surfaces in the Lovable dashboard automatically. Next up, you'll tackle the Secret Mission and add automated KPI health monitoring.
Secret mission
Build an Automated Monday Morning KPI Health Check
What if your system could watch your KPIs while you sleep? In this secret mission, you'll build an automated health check that analyzes KPI trends every Monday morning and posts a summary to Slack. Any KPI below 80% of target or declining for 3 consecutive periods gets flagged before your team even opens their laptops.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
This project uses the Supabase Free Plan, which costs nothing but pauses after 1 week of inactivity. If you upgraded to Supabase Pro ($25/month) or are using Lovable Pro ($25/month), those subscriptions will continue billing until cancelled.
Cost warning
If you upgraded to Supabase Pro, you will be charged $25/month until you downgrade or delete the project. Your Lovable Pro subscription also bills monthly regardless of whether you use the project.
Resources you used:
- Supabase project (database, Edge Functions, secrets, RLS policies).
- Lovable project (dashboard UI, published app).
- GitHub repository (wiki architecture, templates, Edge Function code).
- Slack App (/report slash command, Incoming Webhook).
- Local repository clone on your Desktop.
Keep everything running
No action needed. Choose this if you plan to continue building on this foundation (adding task management, media buying integrations, or the MCP server from the roadmap).
- Your Supabase Free Plan project will pause automatically after 1 week of inactivity. Visit the dashboard at least once a week to keep it active, or upgrade to Pro.
- Your Lovable project remains published and accessible at its URL.
- Your Slack App stays active and the /report command continues to work.
- Your local repo clone remains on your Desktop.
Pause - I'll come back to this later
Shut down running processes and free up resources while keeping your project files and configurations intact.
- Let your Supabase Free Plan project pause naturally after 1 week of inactivity. Your data is preserved and you can restore it later. If you are on Pro, downgrade to Free to stop billing.
- In Lovable, unpublish your dashboard by opening your project, going to Project Settings, and selecting the unpublish option. Your project remains in the editor for future edits.
- Your Slack App, GitHub repo, and local files can stay as they are. They cost nothing and resume instantly when you return.
Delete - I don't want to use this again
Remove all project resources permanently. This cannot be undone.
- Delete your Supabase project: open your project in the Supabase dashboard, go to Project Settings in the sidebar, scroll to the danger zone at the bottom, and click Delete project. Confirm the deletion. This permanently removes your database, Edge Functions, and all stored data.
- Delete your Lovable project: open the project in Lovable, go to Project Settings, scroll to Danger Zone, and click Delete Project. This is permanent and cannot be recovered.
- Delete your GitHub repository: go to your repo on GitHub, click Settings, scroll to the bottom under Danger Zone, and click Delete this repository. Confirm by typing the repository name.
- Delete your Slack App: go to api.slack.com/apps, select your marketing operations app, and use the delete option at the bottom of the app settings page. This removes the /report slash command and the Incoming Webhook.
- Delete your local repository clone by running this command:
macOS
rm -rf ~/Desktop/[[REPO_FOLDER="your-repo-folder-name"]]
Windows
Remove-Item -Recurse -Force ~\Desktop\[[REPO_FOLDER="your-repo-folder-name"]]
That's a Wrap!
That's a Wrap!
Nice work! You've just built the architectural kernel of a fully AI-governed marketing operations platform.
You've learned how to:
- Build a complete LLM Wiki architecture using the Karpathy methodology, with a CLAUDE.md schema, structured document templates, and cross-referenced governance for marketing operations.
- Design and deploy a production Supabase PostgreSQL database with time-series KPI tracking, decision logging, experiment registry, source abstraction, template versioning, and Row Level Security from day one.
- Wire up a full data flow from Slack IC report submissions (via the /report slash command) through a Supabase Edge Function into a Lovable-generated dashboard with KPI views, IC management, report uploads, and a Google Drive creative assets browser.
- Built an automated KPI health monitoring system that flags underperforming metrics and posts a weekly summary to your team's Slack channel every Monday morning.
Ready to quiz yourself?