Skip to content

Onboarding at Lantern โ€‹

From "I got an invite" to a running app, a first pull request, and enough of the codebase to be dangerous. One page, four parts:

  1. What Lantern is, so the code makes sense.
  2. Prereqs: the short list you do by hand, plus what to ask your POC for.
  3. Setup, pick one: 3.A Quick setup hands it to an AI assistant, 3.B Full setup is the same thing by hand.
  4. Additional dev instructions: architecture, workflow, testing, security. Come back to this one; it is not day-one reading.

Part 1: What Lantern is โ€‹

Lantern is an anonymous, real-time people-meeting PWA anchored to physical venues. Users "light lanterns" at bars, cafรฉs, and restaurants to signal availability, not with profiles or photos, but with presence. Think of it as a way to say "I'm here and open to meeting someone" without broadcasting your identity.

The product is currently in a pilot phase: access is controlled at signup, not at app entry. Creating an account requires a single-use invite link (generated by admins in the admin portal's Users tab and validated server-side by the auth API); a visitor without a valid ?invite= token sees an "invite required" screen on #/signup. Signed-in users reach the app normally. The old PILOT_MODE flag and the admin-only AccessGate on app entry have both been removed from the codebase (#385, #375).

For a deeper product understanding, read docs/ABOUT.md. For the business picture (market, roadmap, pricing), start at docs/business/README.md, which points at the canonical plan; individual business docs drift, that index does not.


Part 2: Prereqs โ€‹

Short on purpose. Do these by hand, then let Part 3 install the rest (gh, Node, gcloud and friends are NOT your job here: the AI path installs them, and the by-hand path walks you through them).

Accounts and access โ€‹

  • Accept the GitHub invitation to the cattreedev/lantern_app repository. Check the email associated with your GitHub account, including spam. Until you accept, every link into the repo will 404 for you, which looks like a broken link but is a permissions problem.
  • Turn on two-factor authentication on your GitHub account. This is required, not optional, and you will not be able to contribute without it.

Anything else you need (Google Cloud access, an app account) comes from your POC, your point of contact on the team, the person who brought you on. Nothing except the GitHub invite blocks you from cloning and running the app, so start setup and ask as you go.

Your machine โ€‹

Supported environments are macOS and WSL on Windows. There is no native-Windows path: our scripts and tooling assume a Unix shell. From here down, every OS-specific step is labeled Mac / Windows (WSL) / Both: follow your label and skip the other one.

  • Mac: you are ready as-is. Use Terminal.

  • Windows (WSL): install WSL first. Open PowerShell and run wsl --install, then reboot. Do all of your Lantern work inside the WSL shell from then on, including cloning the repo, and clone into the Linux filesystem (your WSL home directory, like ~/repos/), never under /mnt/c. If you install things on the Windows side by mistake, the tooling will not find them.

  • Windows (WSL): how to tell you are actually inside WSL (a Windows terminal and a WSL terminal look nearly identical, and VS Code's built-in terminal opens PowerShell by default): run uname. If it prints Linux, you are in the right place. If the terminal does not recognize the command, you are still on the Windows side: type wsl and press Enter to drop into Linux, or open the "Ubuntu" app from the Start menu, or in VS Code pick the Ubuntu (WSL) profile from the terminal panel's dropdown arrow. A telltale sign you are in the wrong shell is several commands in a row failing with "command not found": the Windows side has none of the tools this page installs.

  • Both: install VS Code (https://code.visualstudio.com), the team's editor: the repo ships an extension and workspace settings that assume it.

  • Windows (WSL): add Microsoft's WSL extension, which VS Code needs to truly open folders living inside WSL. It is a Windows-side extension, so install it in Windows VS Code, not from your WSL terminal: open the Extensions panel (Ctrl+Shift+X), search WSL, install Microsoft's. (From a Windows PowerShell prompt, code --install-extension ms-vscode-remote.remote-wsl does the same thing. Running that inside WSL fails with "declared to not run in this setup", which is expected and not a broken install.)

    The tell that it is working: when you run code . from a WSL folder, the window's bottom-left corner shows a green WSL: Ubuntu badge. No badge means VS Code opened the files the wrong way (as a Windows window peeking into Linux over a network share, with the wrong terminal and sluggish file watching). If a WSL window already opens with that badge, the extension is installed and you can move on.

  • Mac: put the code command on your PATH (one time; Windows and Linux installs do this automatically, a Mac does not). Paste this into your terminal, then close and reopen the terminal:

    bash
    cat >> ~/.zshrc <<'EOF'
    export PATH="$PATH:/Applications/Visual Studio Code.app/Contents/Resources/app/bin"
    EOF

    The reopen matters: the file you just added to is only read when a terminal starts, so the window you are in does not see the change (running source ~/.zshrc applies it to the current window instead). This path assumes VS Code lives in /Applications, the standard spot; if you are still running it from Downloads, drag it into Applications first. The same wiring is also available from inside VS Code as the Cmd+Shift+P palette command Shell Command: Install 'code' command in PATH. Without one of these, the code . step later says "command not found"; you can always fall back to opening VS Code yourself and using File > Open Folder.

Most of the team works alongside Claude Code. Install it from https://claude.com/claude-code. It is not required to run the app, but the repo ships a lot of shared context that only helps if you have it: opened in the repo, it already knows the project's rules and commands, so you can ask it questions in plain language while you find your feet, and a one-time /context-onboard personalizes it to how you like to work.

What you do NOT need โ€‹

This is the part people get stuck on, so it is worth stating plainly:

  • You do not need any credentials, secrets, API keys, or cloud access to run the app. The public development config ships with the repo. The one sign-in you do need is GitHub itself, to clone the private repo (the gh auth login in Part 3.B step 1). The gcloud CLI from Part 3.B step 3 is an install only: no Google sign-in, no cloud permissions, nothing to wait on. After that: clone, install, run.
  • You do not need Docker or a dev container.
  • You do not need anyone to send you an environment file. If a doc tells you to ask a teammate for one, that doc is out of date.

Cloud access (Google Cloud, secret pulling) only matters later, if and when you work on server-side code. Someone will grant it when you need it.


Part 3.A: Quick setup, let an AI do it โ€‹

If you have an AI assistant that can run terminal commands (Claude Code, Cursor, Copilot agent mode), hand it the whole machine setup instead of walking Part 3.B by hand. It encodes the real traps newcomers have hit (wrong shell, missing profile wiring, nested clones, comment-poisoned pastes), so trust its checks even when they seem fussy. Prefer doing it yourself? Skip to Part 3.B.

Where things live in VS Code, if this is your first time using an AI agent there:

  • The chat/agent panel: for Copilot, click the chat icon in the title bar (or Ctrl+Alt+I, Cmd+Shift+I on Mac) and pick Agent from the mode dropdown at the bottom of the chat box; for Claude Code, click its icon in the left Activity Bar, or just type claude in the terminal. The prompt below gets pasted into that chat box as one message.
  • Copilot users: stay on "Default permissions" (shown at the bottom of the chat panel), not "Autopilot (Preview)". Default mode asks your approval per command and lets you type into the terminal when something like sudo asks for your password. Autopilot auto-approves but cancels any command that asks for input ("sensitive input required"), which dead-ends the install steps on this page.
  • The terminal: Ctrl+` (backtick), or menu View > Terminal. This is where the assistant runs commands, where you approve them, and where YOU will occasionally type (passwords, sudo -v, or a command it hands you).
  • The assistant proposes each command and waits for your approval; approving is normal and expected. When it asks you to run something yourself, run it in the terminal and tell it "done."
  • Two moments are yours even when the agent is driving. Some modes place a command in the terminal and wait for YOU to press Enter there to actually run it, and installers ask their own questions in the terminal (a Y to confirm, which profile file to update) that only you can answer. If the run looks stalled, click into the terminal: something is probably waiting for a keypress.

POCs: the fenced block below is self-contained and needs no repo access to read, so you can paste it into an email or DM before someone has the repo. While you are there, run the grants in GRANTING_LOGINS_AND_ACCESS.md so nothing waits on you mid-setup.

Two notes before you paste:

  • The assistant will need your go-ahead for each install; that is normal and good.
  • The app itself runs with zero credentials; only GitHub sign-in is needed to clone. The final bootstrap step does sign in to Google Cloud and needs your POC to have granted you secret access; if it reports permission errors, that step waits on the grant while everything else keeps working.
text
You are setting up a brand-new contributor's machine for Lantern
(github.com/cattreedev/lantern_app): a React + Vite PWA monorepo with a
Firebase backend, Node 24, npm workspaces. Goal: from bare machine to the app
running locally. Work step by step: check whether each tool already exists
before installing it, say in one line what you are about to do, run it, verify
it worked, then move on. Every step must be safe to re-run.

Platform rules:
- Supported environments are macOS and WSL (Ubuntu) on Windows only. On
  Windows, first verify you are actually inside WSL: uname must print Linux.
  If it does not, help the user install or enter WSL before anything else.
  Never install project tooling on the Windows side.
- Detect the shell with echo $SHELL. zsh (macOS default) uses ~/.zshrc; bash
  (WSL default) uses ~/.bashrc. Whenever an installer claims it updated a
  profile file, verify the lines are really there; if the file did not exist,
  create it and add them yourself, then have the user reopen the terminal or
  source the file.
- macOS ships with neither brew nor apt. Prefer official installers; use
  Homebrew only if it is already present.

The sequence (skip anything already installed and verified):
1. GitHub CLI: install gh (macOS: installer from cli.github.com; WSL: the
   page's apt source lines), then gh auth login over HTTPS, answering yes when
   it offers to configure git credentials. The repo is private, and a VS Code
   or browser GitHub session does not authenticate the terminal.
2. nvm: install per github.com/nvm-sh/nvm, confirm the two startup lines
   landed in the correct profile file, reopen or source, verify nvm --version.
3. Clone: pick a stable home, default ~/repos. If a lantern_app clone already
   exists anywhere, use it instead of cloning again. Never clone while the
   current directory is a lantern_app folder, and never create
   lantern_app/lantern_app. Then: git clone
   https://github.com/cattreedev/lantern_app.git
4. Node: run nvm install INSIDE the repo folder (it reads .nvmrc). npm ships
   inside Node; never install npm separately and never apt install nodejs.
5. Dependencies: npm install at the repo root. EBADENGINE warnings mentioning
   Node 22, deprecation warnings, and npm audit counts are expected noise for
   this repo; do not chase them.
6. npm link at the repo root, so the lantern command exists. If it fails on
   permissions, skip it; ./lantern from the repo root is the fallback.
7. gcloud CLI, installed NON-INTERACTIVELY so nothing waits on the user:
     curl https://sdk.cloud.google.com | bash -s -- --disable-prompts
   Expect a long avalanche of extraction lines; that is normal, not a hang.
   Because prompts are disabled, the installer does NOT wire your PATH, so
   you must do it: append the two lines matching the shell you detected, only
   if they are not already there. On WSL/bash, into ~/.bashrc:
     source "$HOME/google-cloud-sdk/path.bash.inc"
     source "$HOME/google-cloud-sdk/completion.bash.inc"
   On macOS/zsh, into ~/.zshrc, using the .zsh.inc files instead:
     source "$HOME/google-cloud-sdk/path.zsh.inc"
     source "$HOME/google-cloud-sdk/completion.zsh.inc"
   then source the profile and verify with gcloud --version. Same repair if
   an older interactive install left the binary at
   ~/google-cloud-sdk/bin/gcloud while `command -v gcloud` fails: append the
   lines rather than re-running the installer.
8. VS Code: confirm the app is installed and the code command works in the
   terminal. On macOS that means the app lives in /Applications and its
   Resources/app/bin directory is on PATH (add it to ~/.zshrc), or the user
   runs the in-app palette command "Shell Command: Install 'code' command in
   PATH". On WSL, do NOT try to install ms-vscode-remote.remote-wsl from the
   WSL terminal: it is a Windows-side extension and the install correctly
   fails with "declared to not run in this setup" (verified in testing). If
   the user is already working in a VS Code window whose bottom-left badge
   reads "WSL: <distro>", that extension is present and there is nothing to
   do; otherwise tell the user to install it in WINDOWS VS Code (Extensions
   panel, search "WSL") and reopen the folder from WSL.
9. Lantern Control extension (required): npm run vscode-ext:install from the
   repo root. It packages the extension and installs it via the code command.
   To verify from the CLI, match the publisher-qualified id: code
   --list-extensions | grep -i cattreedev.lantern-control (a bare
   "lantern-control" will not match).
   If the final install step fails on code not being found, fix step 8 and run
   code --install-extension tooling/vscode-extension/lantern-control.vsix
   --force, or install the VSIX through the VS Code Extensions panel's
   "Install from VSIX" menu.
   A CLI listing is NOT proof the user can use it: VS Code does not show a
   freshly installed extension until the window reloads. So close the loop
   with them rather than leaving it as an open item on your summary. Tell
   them: reload the window (Ctrl+Shift+P / Cmd+Shift+P, "Developer: Reload
   Window"), then look for the Lantern Control icon in the left Activity Bar,
   and say whether they see it. If they do not after a reload, re-run the
   --force install above and have them reload again. Only then treat step 9
   as done.
10. Credentials bootstrap (required): gcloud auth login, then gcloud auth
    application-default login, then npm run env:bootstrap at the repo root,
    which writes .env.local from Secret Manager. If env:bootstrap reports
    permission errors on every secret, that is the expected symptom of a
    pending access grant, not a broken setup: their POC must grant them
    roles/secretmanager.secretAccessor on lantern-app-dev (the exact commands
    live in docs/engineering/guides/onboarding/GRANTING_LOGINS_AND_ACCESS.md).
    Tell the user to request that grant, continue with step 11 in the
    meantime, and re-run npm run env:bootstrap once the grant lands.
11. Verify: run lantern doctor (or ./lantern doctor) from the repo and resolve
    what it flags; if step 10 is still waiting on the POC grant, yellow
    .env.local and gcloud lines are the expected state. Then npm run dev and
    confirm http://localhost:5173 loads.
12. Hand off warmly. Do not stop at "setup complete": the user has a running
    app and no idea what to do with it. Close with a SHORT, friendly
    orientation in your own words, covering:
    - What is running and where: the app on :5173, and, when they want them,
      npm run storybook (:6006, the component library) and
      npm run dev -w apps/admin (:3001, the admin portal). Ctrl+C stops a
      dev server; npm run dev starts it again.
    - Try it now: Lantern is an anonymous, venue-based app for meeting people
      in real places, so browsing venues and lighting a lantern is the core
      loop worth clicking through. Creating an account needs a single-use
      invite link from their POC, so tell them to ask for one if they want
      to sign in rather than just look around.
    - The Lantern Control sidebar they just installed: have them click its
      icon in the Activity Bar (after the step 9 reload) and confirm they see
      it. If the icon is still missing, fix that here rather than shipping it
      as an open item. Explain the difference that trips people up: the
      npm run dev they already have runs ONLY the app, while the sidebar's
      Quickstart starts the whole local stack (app, admin, all APIs, docs
      site, public site, Storybook, test watcher), which is a lot for day one.
      Tell them to stay on npm run dev until they need the rest, and that
      emulators are deliberately not in Quickstart because the Firestore
      emulator and the venues API both want port 8080.
    - Make the AI theirs: run npm run context:bootstrap at the repo root, then
      /context-onboard once in Claude Code to set up personal context (how they
      like to be worked with). It is machine-local and never committed. Tell
      them bootstrap also drops a personal onboarding-checklist.md into
      ~/.agents/context/lantern_app/, a tick-box copy of this whole page that
      is theirs to scribble on, and print the full path so they can open it.
    - The first-PR shape, in one breath: branch from origin/dev, do the work,
      run npm run validate ONCE when it is ready, then open the PR as a DRAFT
      (drafts skip CI, which costs real money on this private repo) and let a
      a senior engineer mark it ready. Point at the first-PR section of this page for the exact
      commands.
    - Where to read next: Part 4 of the onboarding page (architecture, how
      we work, testing, security) once they are running, and AGENTS.md for
      the rules humans and agents share.
    - Anything still pending on their POC (typically the Secret Manager
      grant), named as a wait rather than a failure, with what it unblocks.
    End by asking what they want to look at first, and offer to walk them
    through it. Keep the whole thing scannable; this is a welcome, not a
    manual.

Hard rules:
- sudo WILL prompt for the user's Linux password, and an autonomous terminal
  cannot and must not answer it. Do not assume a user-typed `sudo -v` helps:
  sudo's authorization is per-terminal, and if you run commands in your own
  shell rather than the user's terminal, the ticket does not reach you
  (verified in testing). The reliable pattern: the FIRST time any command of
  yours is cancelled for a password or "sensitive input", stop retrying that
  route entirely; from then on, print each sudo-bearing command for the user
  to paste into their own terminal, wait for them to say done, and continue
  from the verification step. There are only a handful of sudo steps on this
  page, so this costs the user a few pastes at most. Never ask the user to
  tell you their password.
- Run one step at a time as separate commands; do not concatenate several
  installs into a single set -e script, because one password prompt or hiccup
  then cancels the whole chain and leaves you unsure what completed.
- PREFER THE NON-INTERACTIVE FORM of any command that has one, so the user is
  never the bottleneck: --disable-prompts for the gcloud installer, -y for apt.
  Only go interactive when there is no alternative (the sign-ins below).
- When a command genuinely must be interactive (gh auth login, gcloud auth
  login, gcloud auth application-default login), send a SEPARATE, short
  message immediately before you run it, in this shape, so it is impossible to
  miss:
      ACTION NEEDED IN THE TERMINAL: <what to do>, e.g. "press Enter, then
      answer Y twice" or "your browser will open, sign in, then come back".
  Do not bury that inside a paragraph of narration, and do not send it after
  the command; the user is watching the chat, not the terminal, and a waiting
  prompt looks identical to a frozen one. Then stop and wait for them.
- If any command sits with no new output, assume it is WAITING, not hung: tell
  the user to click into the terminal and press Enter or answer the prompt.
  Some agent modes stage a command and wait for the user's Enter to run it at
  all, so nothing happens until they act.
- If a browser never opens for a sign-in (common on WSL), re-run that command
  with --no-launch-browser and have the user open the printed URL and paste
  the code back into the terminal.
- Never run a destructive command (rm -rf, git reset, overwriting dotfiles)
  without showing the user the exact target and getting an explicit yes.
- Only append to profile files, never rewrite them.
- Never write .env.local by hand; only npm run env:bootstrap creates it.
- Running the app needs no credentials: a pending secret-access grant only
  delays step 10, never npm run dev. Never tell the user the app is blocked
  on credentials.
- The local admin portal (port 3001) cannot log in until step 10 has fully
  succeeded; do not debug admin sign-in before then.
- If a step fails twice, stop and summarize the exact state and error instead
  of improvising around it.

Part 3.B: Full setup, by hand โ€‹

The same destination as 3.A, done yourself. Steps 1 to 3 install the toolchain; 4 to 7 get the app running; 8 is only for backend work later.

1. Install the GitHub CLI and sign in โ€‹

The repo is private, so the moment you git clone it, git asks who you are. Being signed into GitHub in your browser or in VS Code does not carry over to the terminal: those logins live inside their own apps, and a plain shell cannot see them. The fix is a one-time sign-in with GitHub's official command-line tool, gh:

  • Mac: install it from https://cli.github.com using the Mac installer on that page. (If you already use Homebrew, brew install gh works too, but brew is not built into a Mac, so if that command says "not found" just use the installer. Homebrew itself lives at https://brew.sh and is worth having eventually; do not let it block you today.)
  • Windows (WSL): install it by following that page's Linux instructions, a few copy-paste lines that add GitHub's package source, then sudo apt install gh. Run them inside WSL, not on the Windows side, and skip anything mentioning brew: that is the Mac path.
  • Both: sign in once. Run gh auth login, pick GitHub.com and HTTPS, and let it open the browser. When it offers to configure git credentials, say yes: that is the part that lets git clone and git push work without ever asking again.

This is not a one-off install. The team uses gh day to day for pull requests, issues, and CI checks from the terminal, so it will keep earning its place.

2. Install Node.js 24 โ€‹

Lantern runs on Node.js 24. Installing Node also gives you npm: Node's package manager ships inside Node itself, so there is no separate npm install anywhere on this page, and you should not go looking for one. The easiest way to get the exact Node version is a version manager:

  • Both: install nvm (https://github.com/nvm-sh/nvm): copy the one-line install command from that page into your shell, on macOS or inside WSL. Then close and reopen the terminal, because the window you installed from cannot see nvm yet.

  • Both: if nvm still says "command not found" after reopening, nvm did not get wired into your shell's startup file. The installer tries, but it only succeeds when that file already exists, and a brand-new Mac usually has no ~/.zshrc yet (the installer does say so, in output that is easy to scroll past). Check which shell you have with echo $SHELL, then paste the matching block below into the terminal: it creates the file if needed and adds nvm's two startup lines. Reopen the terminal once more and nvm --version should answer.

    Mac: if echo $SHELL ends in zsh (the macOS default):

    bash
    cat >> ~/.zshrc <<'EOF'
    export NVM_DIR="$HOME/.nvm"
    [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
    EOF

    Windows (WSL): if it ends in bash (the Ubuntu default):

    bash
    cat >> ~/.bashrc <<'EOF'
    export NVM_DIR="$HOME/.nvm"
    [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
    EOF
  • Both: after you clone, run nvm install inside the repo folder and it reads our pinned version automatically. The moment it finishes, npm works too.

  • Windows (WSL): never sudo apt install nodejs or sudo apt install npm: Ubuntu's packaged copy is years out of date and will shadow the correct one.

You can check what you have with node --version and npm --version.

3. Install the Google Cloud CLI โ€‹

Lantern's backend lives on Google Cloud, and our tooling (including the lantern doctor health check you will meet on day one) expects the gcloud command to exist. Both, identical on the two platforms; install the tool now, and you do not need to sign in to anything yet:

bash
curl https://sdk.cloud.google.com | bash -s -- --disable-prompts

Expect a long avalanche of x google-cloud-sdk/... lines: that is the installer unpacking thousands of files, and every one of them prints. It is normal; wait it out.

--disable-prompts means it never stops to ask you anything, which is the point: the interactive version pauses on questions that are easy to miss. The tradeoff is that it does not touch your $PATH, so add it yourself. Paste the block for your shell, then close and reopen the terminal:

Mac (zsh):

bash
cat >> ~/.zshrc <<'EOF'
source "$HOME/google-cloud-sdk/path.zsh.inc"
source "$HOME/google-cloud-sdk/completion.zsh.inc"
EOF

Windows (WSL) (bash):

bash
cat >> ~/.bashrc <<'EOF'
source "$HOME/google-cloud-sdk/path.bash.inc"
source "$HOME/google-cloud-sdk/completion.bash.inc"
EOF

Then gcloud --version should answer. Other install options live at https://cloud.google.com/sdk/docs/install. Signing in and cloud access come much later, only if and when you work on server-side code, and step 8 below tells you when that moment arrives.

4. Clone and install โ€‹

git clone creates a lantern_app folder inside whatever directory you run it from, so first move to where you want the repo to live. Not sure where you are? pwd prints it. On WSL, if pwd starts with /mnt/c you are in Windows-mounted territory (the hazard from section 0): run cd ~ to get back to your Linux home before cloning.

bash
mkdir -p ~/repos
cd ~/repos
[ -d lantern_app ] || [ "$(basename "$PWD")" = "lantern_app" ] || git clone https://github.com/cattreedev/lantern_app.git
cd lantern_app
npm install
npm link
code .

Line by line: the first two commands create (if needed) and enter ~/repos, a stable home for your repos, so the clone lands at ~/repos/lantern_app. The clone line is double-guarded: it skips itself when the folder already exists here, and it refuses to run when you are already standing inside a lantern_app folder, because git would otherwise happily nest a second copy at lantern_app/lantern_app (a surprisingly easy accident when re-running these steps). npm install covers the whole monorepo in one go. npm link is a one-time step that puts the lantern command on your PATH. code . opens the clone in VS Code; on Windows, check the window's bottom-left corner for the green WSL: Ubuntu badge, which means VS Code is properly connected to WSL. If the badge is missing, the WSL extension from Part 2 has not been installed yet: install it in Windows VS Code (Extensions panel, search "WSL"), close that window, and run code . again. Installing it from inside WSL does not work, by design. If code says "command not found" on a Mac, do the one-time PATH step in Part 2, or simply open VS Code yourself and use File > Open Folder to open the repo folder.

If the clone asks you to log in (or says gh: command not found when you try to fix that), it means the GitHub CLI step (step 1 above) has not run yet. The repo is private, and being signed into GitHub in VS Code or a browser does not reach a plain terminal: those logins live inside their own apps. Install gh, run gh auth login, say yes when it offers to configure git credentials, then rerun the clone.

Running this page a second time (retrying after a hiccup, or starting over)? The block above is safe to paste again as a whole: the clone line skips itself when the repo is already there or when you are already inside it, and the install and link steps are naturally re-runnable. One exception: if you deliberately cloned into a different folder (say ~/Projects), do not paste the block again, because it would create a second clone under ~/repos; instead cd into your existing clone and continue from npm install. The same principle applies page-wide: a command answering with some form of already exists is a step reporting itself done, not an error, so skip it and continue with the next line. Only delete and re-clone (rm -rf lantern_app, the -f matters because git's internal files are write-protected) if you deliberately want a factory-fresh copy, and know that this throws away any local changes inside it.

When VS Code opens, accept the recommended-extensions prompt, and do the rest of this page from its integrated terminal (Ctrl+`), which starts in the repo folder.

The install output looks alarming. It is fine. โ€‹

A healthy npm install here prints several things that look like errors and are not:

  • EBADENGINE warnings mentioning Node 22. The Cloud Functions workspace (services/functions/firebase) deliberately pins Node 22 because that is the runtime Google gives us, while the rest of the repo runs Node 24. Expected. (The eventual bump is tracked in #838; do not "fix" it locally.)
  • Deprecation warnings from transitive dependencies we do not control.
  • npm audit vulnerability counts. Production dependencies are audited separately in CI; this number does not block local dev.

If npm install finishes and returns your prompt, it worked.

Get your own checklist โ€‹

Optional, and genuinely useful if you like ticking things off:

bash
npm run context:bootstrap

That creates your personal context home at ~/.agents/context/lantern_app/ and, inside it, onboarding-checklist.md: a tick-box copy of this whole page, from the GitHub invite through your first PR. It is yours, on your machine, never committed, so mark it up however you like. The command is safe to re-run and never overwrites a file you already have, so an existing checklist with your ticks in it survives.

The same home is where /context-onboard (in Part 4) stores how you like to be worked with, so running this now means that step has somewhere to write later.

5. Check your environment โ€‹

bash
lantern doctor

doctor works from any directory once step 4's npm link has run. It checks node, dependencies, gcloud, .env.local, and git hooks, and prints the exact next command for anything it finds. Yellow lines about .env.local or gcloud are expected on a fresh machine; they only matter for backend work (step 8). A yellow lantern on PATH row just means npm link has not run for this clone. None of them block running the app.

Why the bare lantern works, and the fallbacks. Step 4's one-time npm link puts the lantern command on your PATH, pointed at this clone; that is the documented form. Two fallback cases: if npm link errored (some global-npm setups hit a permission wall), or you keep several clones around (the link targets exactly one), use ./lantern <cmd> from the repo root instead, which always works with zero setup.

Never use npx lantern: an unrelated public package named lantern exists on the npm registry, so npx from outside the repo root downloads a stranger's package. And npm run lantern breaks inside workspace directories (apps/web, services/api/*, packages/*), where npm resolves that workspace's own package.json, which has no such script.

6. Run the app (no secrets needed) โ€‹

bash
npm run dev

The app serves at http://localhost:5173. The public dev Firebase config is committed in .env.development, so it boots against the shared dev backend with zero setup. Open the URL and you have Lantern running.

Other surfaces: npm run storybook (component library, :6006) and npm run dev -w apps/admin (admin portal, :3001).

One caveat on the local admin portal: its sign-in goes through the auth API using configuration that only exists after step 8, so on a fresh machine it renders but cannot log you in. The telltale console lines are VITE_AUTH_API_URL is not set and a 404 for /undefined/auth/admin/signin; an "App Check not configured" warning is expected noise in the same state. Until you have done step 8, use the deployed dev admin portal with the admin-dev login your POC granted you.

7. Install the Lantern Control sidebar โ€‹

Lantern Control is our own VS Code extension: a sidebar that starts and stops the local services, runs common scripts, checks your environment, and links the docs. Install it from the repo root:

bash
npm run vscode-ext:install

It builds the extension and installs it via the code command, so code must be on your PATH (on a Mac, the one-time PATH step in Part 2). The deprecation and audit warnings it prints are the same expected noise as step 4's npm install.

If the run ends with code: command not found, the build itself still succeeded. Either do the PATH step, reopen the terminal, and run the install half directly:

bash
code --install-extension tooling/vscode-extension/lantern-control.vsix --force

or skip the command line entirely: in VS Code open the Extensions panel, click the ... menu at its top right, choose Install from VSIX..., and pick tooling/vscode-extension/lantern-control.vsix.

Then reload VS Code (Cmd+Shift+P or Ctrl+Shift+P, "Developer: Reload Window") and a Lantern Control icon appears in the left Activity Bar. Click it to open the sidebar: per-service start/stop, common scripts, environment checks, and doc links.

One thing to know before you click Quickstart. npm run dev from step 6 starts only the Lantern app, which is all you need for a while. The sidebar's Quickstart starts the whole local stack at once: the app (:5173), the admin panel (:3001), all the APIs, the docs site, the public site, Storybook (:6006), and a test watcher. That is genuinely useful once you work across services, and it is a lot of terminals and memory on day one, so start with npm run dev and reach for Quickstart when you actually need the rest. Stop anything from the same sidebar.

Emulators are deliberately not in Quickstart: the Firestore emulator and the venues API both want port 8080, so they cannot run together. Start emulators only when you are testing Firebase rules or functions directly.


8. Later: credentials for backend work โ€‹

Skip this until you touch server-side code or anything needing real credentials.

The gcloud CLI itself is installed back in step 3; if this machine is missing it (gcloud --version fails), that section has it. The credential sequence, in order:

bash
gcloud auth login
gcloud auth application-default login
npm run env:bootstrap

The first command signs you into Google Cloud and the second creates the local credentials our tools read (in Codespaces, add --no-launch-browser to both). env:bootstrap then writes .env.local from Secret Manager.

Between the login and the bootstrap, your POC has to grant you roles/secretmanager.secretAccessor on the lantern-app-dev project. Until they do, env:bootstrap reports a permission error for every secret. That is the expected symptom of a missing grant, not a broken setup: ask your POC, then re-run it.


Part 4: Additional dev instructions โ€‹

Not day-one reading. Come back once the app is running.

First hour: what to actually do โ€‹

The app is running and nothing else is blocking you. A good first hour:

  • Click around the running app at http://localhost:5173. Lantern is an anonymous, venue-based way to meet people in real places, so browse venues and light a lantern: that is the core loop, and seeing it beats reading about it. Signing in needs a single-use invite link (signup is invite-gated during the pilot), so ask your POC for one when you want an account rather than just a look around.
  • Meet the other two surfaces when you are curious: npm run storybook (:6006) is every UI component on its own, which is the fastest way to learn the design system, and npm run dev -w apps/admin (:3001) is the admin portal. Ctrl+C stops a dev server.
  • Make the AI yours: run /context-onboard once in Claude Code. It asks how you like to be worked with and stores that on your machine (never committed), so every future session starts knowing your preferences instead of guessing. It writes into the same context home as npm run context:bootstrap, where your personal onboarding checklist also lives.
  • Ship something small. The first-PR commands are in Your first pull request just below. A tiny doc fix is a perfectly good first PR and gets you through the whole flow (branch, validate, draft PR) while the stakes are low.
  • Ask early. If something here was confusing or wrong, say so: setup friction is a documentation bug, and the fastest fix is you telling us while it is fresh.

Your first pull request โ€‹

bash
git fetch origin dev
git checkout -b <type>/<issue>-<topic> origin/dev

Open the draft PR right away, before the work, then build on the branch:

bash
git push -u origin HEAD
gh pr create --base dev --draft --title "..." --body "..."

When the work is ready, clear the gate and ask for review:

bash
npm run validate
gh pr ready

Four things worth internalizing now:

  • Branch from origin/dev, not your local dev, which drifts silently behind the remote.
  • Open the PR as a draft, and open it EARLY (rule 12). Drafts skip CI, and CI costs real money on this private repo, so an early draft is free and gives your reviewer a live diff to watch. Mark it ready once, when the work is done.
  • npm run validate is the pre-PR gate, run once when your work is ready. Anything telling you to run it before every commit is out of date.
  • Nothing merges without a senior engineer's approval. When your PR is ready, request review AND tell them directly; an unwatched PR is how a bad merge happens. Expect the reviewer to read the diff and ask real questions. That is the job working, not distrust of you.

How the codebase is laid out โ€‹

Monorepo Structure โ€‹

apps/
  web/           - Main Lantern PWA (React 19 + Vite)        โ† you'll work here most
  admin/         - Admin dashboard (separate React app)

services/
  functions/firebase/  - Cloud Functions (Node 22)
  api/analytics/       - Analytics API (Cloud Run)
  api/venues/          - Venues API (Cloud Run)
  api/docs/            - Docs API (Express.js, Cloud Run)
  bots/discord/        - Discord bot for issue triage

packages/
  shared/        - Shared constants & utilities (@lantern/shared)
  forge/         - Shared BigQuery, Firestore, rate limiting, sanitization helpers

tooling/
  .storybook/    - Storybook configuration
  scripts/       - Build, lint, and utility scripts
  schemas/       - BigQuery event schemas

Web App Internals (apps/web/src/) โ€‹

PathPurpose
App.jsxRoot component: routing, auth gate (admin-role check via checkAdminRole)
firebase.jsFirebase initialization, environment detection, persistence
screens/{domain}/Page-level components: auth/, dashboard/, profile/, merchant/, frens/
components/Reusable UI components (mostly Storybook-backed)
lib/Service modules: auth.js, encryption.js, venueCacheManager.js, etc.
hooks/Custom React hooks

Routing โ€‹

Lantern uses hash-based routing: no React Router. Routes are defined as window.location.hash checks in App.jsx:

HashScreen
#/Dashboard (Lantern Hub)
#/signupSign up
#/loginLogin
#/profileProfile settings
#/onboarding/profileFirst-time profile setup
#/merchantMerchant dashboard
#/merchant/newNew merchant setup
#/frensFriends
#/feedbackFeedback

To add a new route, update the route checks in App.jsx.

State Management โ€‹

No external state library. State flows through React hooks and Firestore real-time listeners (onSnapshot). Auth state is managed in App.jsx via onAuthChange. Venue data is cached in localStorage via venueCacheManager.js.

Shared Package โ€‹

packages/shared/ is aliased as @lantern/shared:

javascript
import { USER_ROLES, hasRequiredRole } from '@lantern/shared/auth'
import { isDevelopment } from '@lantern/shared/utils'

Zero-Knowledge Encryption โ€‹

User personal data is encrypted client-side before reaching Firestore:

  • Algorithm: PBKDF2 (600,000 iterations) + AES-256-GCM
  • Keys are derived from the user's passphrase and are never sent to the server
  • If a user loses their passphrase, their data is unrecoverable (by design)
  • Implementation: apps/web/src/lib/encryption.js

Critical distinction: The user's Lantern passphrase is also their Firebase Auth password AND their encryption key source. Do not conflate this with the admin portal password (see Admin Authentication below).

Firebase Collections โ€‹

CollectionPurposeTTL
usersProfiles (public fields + encrypted personal data)Permanent
lanternsActive check-ins2 hours active (LANTERN_DURATION_MS); the doc lingers up to ~48h for cleanup but is expired and hidden after 2h
wavesMeeting requests7 days
connections (+ messages subcollection)Private 1:1 threads; message bodies are E2EE90 days idle
venuesLocations (imported from OpenStreetMap)Permanent
offersMerchant promotionsPermanent
adminProfilesAdmin-roleโ€“specific data + separate password hashPermanent
merchantProfilesMerchant-roleโ€“specific dataPermanent

Security rules live in firestore.rules (Firestore) and storage.rules (Firebase Storage).


How we work: branches, commits, PRs โ€‹

The Git Flow โ€‹

All changes go through pull requests. Direct pushes to dev or main are not allowed.

feature branch โ†’ PR โ†’ dev โ†’ (auto-deploy to dev.ourlantern.app)
dev โ†’ PR โ†’ main โ†’ (auto-deploy to ourlantern.app)

Your daily flow:

bash
# 1. Branch off origin/dev (NOT local dev, which drifts silently behind the remote)
git fetch origin dev
git checkout -b <type>/<issue>-<topic> origin/dev

# 2. Open the draft PR EARLY (rule 12): one branch, one draft PR, opened before the work
git push -u origin HEAD
gh pr create --base dev --draft --title "..." --body "..."

# 3. Make changes; run the app and the fast checks as you go
npm run dev           # local dev server
npm test              # watch-mode unit tests

# 4. Commit (reference the issue) and push each logical chunk as you go
git commit -m "feat(auth): add passphrase strength indicator (fixes #123)"
git push

# 5. When the work is READY, run the gate once, then mark the draft ready for review
npm run validate      # the pre-PR gate (lint + tests + coverage + audit)
gh pr ready

Three rules that trip people up, all deliberate: branch from origin/dev (rule 15), open the PR as a draft (rule 16), and open that draft early, keeping one branch and one PR for the whole session (rule 12). Drafts skip CI, which costs real money on this private repo, so an early draft is free; mark the PR ready once, when the work is done and the gate is green.

Before You Mark the PR Ready โ€‹

bash
npm run validate

This runs: lint โ†’ format check โ†’ unit tests โ†’ coverage โ†’ security audit โ†’ all workspace validations. Fix anything that fails before pushing for review.

Run it once, when your work is ready, not before every commit. It is a long gate (it includes headless-browser story tests), and running it per commit would slow you to a crawl for no benefit. Commit freely as you work; the gate is the thing you clear before asking for review.

PR Rules โ€‹

  • Target the dev branch (not main)
  • Open it as a draft (--draft), early, at the start of the work (rule 12); mark ready once when the work is done
  • Reference the GitHub issue: fixes #123 or closes #123
  • Keep PRs small and focused: one feature or fix per PR
  • All CI checks must pass before merging (lint, tests, CodeQL, dependency scan)
  • Sensitive features (auth, encryption, payments) require a security reviewer

Security checklist โ€‹

Complete this before your first commit to any production-connected system. It applies to contractors and vendors too, and access to production infrastructure additionally needs sign-off from a security reviewer, not just the grant itself.

If you find a security issue, do not post it publicly. Follow the vulnerability disclosure process.

These same items are section 8 of the personal checklist that npm run context:bootstrap puts in your context home, so you can tick them there.

Admin Authentication โ€‹

Important: Admin portal authentication is completely separate from the Lantern app passphrase, and the two must never be merged.

If you are being set up as an admin, an existing admin creates your account and you get a setup link (?mode=adminReset&token=xxx, valid 24 hours, single use) where you set your admin portal password. Keep it separate from your Lantern passphrase: the app passphrase is also your encryption key, so a routine admin password reset would otherwise destroy your app data.

Why the two systems are separate, and how the admin side actually works, is in SECURITY_ARCHITECTURE.md.


Testing โ€‹

Framework โ€‹

  • Vitest + Testing Library React + jsdom
  • Coverage threshold: 75% on lines, functions, branches, and statements

Where Tests Live โ€‹

apps/web/src/**/*.{test,spec}.{js,jsx}

Running Tests โ€‹

bash
npm test                  # Watch mode (during development)
npm run test:coverage     # Single run with full coverage report

Path Alias โ€‹

In tests, @ maps to apps/web/src/:

javascript
import { encrypt } from '@/lib/encryption'

Setup File โ€‹

apps/web/test.setup.js provides DOM polyfills and mocks (e.g., IntersectionObserver). If a test needs a new global mock, add it here.

Writing Tests โ€‹

  • Write tests for any new behavior you add
  • Unit test service modules in lib/ and hooks in hooks/
  • Component tests use Testing Library (prefer user-behavior queries over implementation details)
  • Run npm run test:coverage before your PR to verify you haven't dropped coverage below 75%

Documentation practices โ€‹

Where Documentation Lives โ€‹

All documentation lives in docs/. Never put docs in src/ or other source directories. The canonical guide to what goes where is:

docs/engineering/guides/DIRECTORY_DEFINITIONS.md: read this before creating any new doc

Naming Conventions โ€‹

TypeConventionExample
Regular docsCAPS_SNAKE_CASE.mdFEATURE_NAME.md
Worklog entriesYYYY-MM-DD_FEATURE_OUTCOME.md2026-03-13_WAVE_COMPLETE.md
Audit reportsAUDIT_YYYY-MM-DD.mdAUDIT_2026-03-13.md

Changelog โ€‹

Every PR should include a changelog entry. Add it to the daily section in docs/changelogs/dev/:

bash
# File: docs/changelogs/dev/dev-MM.DD.YYYY-v{version}.md

Use present tense: "Add feature" not "Added feature". See docs/engineering/guides/CHANGELOG_WORKFLOW.md for the full format.

Documentation Types โ€‹

TypeLocationWhen to Use
Feature specsdocs/features/{name}/New user-facing feature
Engineering guidesdocs/engineering/guides/How-to, workflows, operational procedures
Worklogsdocs/worklog/Completed work (2+ hours of significant work)
Plansdocs/planning/plans/Roadmaps, large-scale planning
Architecture decisionsdocs/engineering/architecture/System design

Environments โ€‹

EnvironmentFirebase ProjectURLBranch
Locallantern-app-devlocalhost:5173Any
Developmentlantern-app-devdev.ourlantern.appdev
Productionlantern-app-prodourlantern.appmain

Environment Detection โ€‹

The app detects its environment via VITE_APP_ENV and hostname checks. When adding dev-only UI (debug panels, simulate buttons, etc.):

javascript
const hostname = typeof window !== 'undefined' ? window.location.hostname : ''
const isProductionEnv = import.meta.env.VITE_APP_ENV === 'production' || hostname === 'ourlantern.app'
const isDev = !isProductionEnv

Dev Utilities โ€‹

In dev mode, useful utilities are exposed on window (via apps/web/src/main.jsx): location spoofing, venue testing, and cache management. Open the browser console and type window. to see what's available. These utilities are stripped from production builds.


Commands, rules, and conventions live in AGENTS.md โ€‹

Every npm script, the port table, the deployment rules, the PR label mapping, and the linting conventions are in AGENTS.md, which humans and AI agents both read. They are deliberately NOT repeated here: one copy that is always right beats two that disagree. npm run help prints the same script list from your terminal.


Where to go next โ€‹

If you got stuck anywhere on this page, say so: setup friction is a documentation bug, and the fastest fix is you telling us while it is fresh.

Built with VitePress