Files
beardrive/web/docs
1c703c95f8 feat(templates): start a project from a structure, not an empty folder (#97)
* feat(templates): start a project from a structure, not an empty folder

A new project was an empty folder with a .bdriveignore in it, so every agent
session invented its own layout and the folder rotted into a pile. Both
surfaces now offer the same three starting points — from a template, from
scratch, from an existing folder (which is just a non-empty folder, and is
never restructured).

internal/templates holds the shipped set as literal go:embed'ed files: `docs`
(docs/, decisions/) and `para` (projects/, areas/, resources/, archives/).
cmd/bdrive is one binary for the CLI and the hub, so both read the identical
set — no gallery, no drift. The AGENTS.md in each is the deliverable: where a
new note goes, when something is archived, what a good filename looks like.
Every directory holds a real file, because BearDrive syncs paths and an empty
directory would never reach a teammate.

The hub seeds at creation through the existing Upload+Commit path, journaled
under its own device, and records the choice on the project record — so a user
who picked PARA in a browser sees PARA in the browser, and a later init cannot
seed a second copy. `bdrive init --template <name>` goes through the same
endpoint, with a local-seed fallback for a hub too old to know the field, and
seeds in place when re-run in an already-initialized folder (the agent's
post-init path). Seeding never overwrites an existing path, which is what makes
a double-seed a no-op rather than a divergence.

Refusals cost nothing: an unknown name and --template with --only are both
rejected before any network call or write.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(cli): joining a project that already has a template is refused by name

The one acceptance case with no test behind it: connecting to an existing
project with --template must say what the project was actually created from,
and must not write the other skeleton on the way out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(templates): name the docs template in plain English, not an acronym

"Plain docs + ADRs" was the recommended, first, preselected-adjacent option in
a picker that non-engineers see — and it's the label people accept without
reading further, so half of it not parsing is the worst place for jargon. The
title also disagreed with its own blurb: "ADRs" over "docs/, decisions/", two
words for the same folder one line apart.

Now "Docs + decision records", which says the same thing to everyone and
matches the folder names. The term itself moves into
decisions/0001-record-decisions.md, where the reader is already inside the
structure and the file can teach it in passing.

One line in the registry drives both the web dialog and the CLI menu; the rest
is prose echoing it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(templates): add the LLM wiki template

The third starting point from the issue title, unblocked: the spec parked it
because shipping an approximation under someone's name needed a source, and
there is now one — Karpathy's LLM Wiki gist. Worth noting the issue's own
one-line description of it ("few large, append-heavy topic pages") does not
match the source, which is the opposite: many interlinked pages, where a single
ingest touches 10-15 of them.

The pattern is three layers and three operations, not a folder shape. sources/
is yours and immutable; wiki/ is the agent's and it owns every page; AGENTS.md
is the schema layer — which is exactly the file this template system already
treats as the deliverable, so the fit is direct. index.md and log.md ship as
the two navigation files the pattern turns on.

Three of the things the gist tells you to go set up, BearDrive already is:
version history and collaboration (per-file history, bdrive log), an Obsidian-
style reader for [[wikilinks]] (the hub viewer), and a surface for the lint
pass (the dashboard is literally reads x staleness).

Two rules in the AGENTS.md are load-bearing and deliberate. A page write that
has not updated the index is an incomplete write — a stale index is worse than
a missing page, because it is read first and believed. And with no sources yet,
build nothing: the structure grows out of the material rather than ahead of it.

Shipped second, not first: docs stays the recommendation because a default is
the option chosen by people not reading closely, and this pattern degrades
badly when half-followed. Promoting it later is one line in the registry.

The shipped-template test now checks the "what happens when something stops
being true" question through a set of alternatives — PARA archives, a wiki
supersedes and revises — since the vocabulary honestly differs by structure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(web): "I already have a folder" as a starting point

The third way to start from the spec — adopt what you already have — had no
presence in the browser. Templates and "empty" were the only visible answers,
so someone with a folder of notes either hesitated or picked a template and got
four directories merged into their material.

The constraint that shapes it: the browser cannot reach your disk, so this
cannot change what is created. It creates the same empty project "Empty
project" does; what it changes is the next screen. Create therefore stays
enabled — disabling it would leave the dialog a dead end AND produce no project
id, which is the one thing the paste prompt actually needs.

Landing on the project home with the intent, three things differ: the guide
says "in the folder you already have", a note states plainly that connecting
never moves, renames or overwrites anything, and the paste prompt tells the
agent a folder already exists. That last one is the part that isn't cosmetic —
without it an agent reads an empty project and proposes creating shared/, the
one recommendation that is wrong here. It still asks which folder: that is the
runbook's hard gate and nothing here weakens it.

The intent rides in the URL (?connect=existing) rather than onto the project
record, the same way ?v= pins a file version. It belongs to whoever is
connecting right now — a teammate who connects next week has their own answer
and would be told the wrong thing by a persisted flag.

Five rows made the dialog tall enough to push Create off a short viewport, so
.modal scrolls internally. A hairline divider between the seeding and
non-seeding rows was tried and removed: --border is 7% white, which at 1px in a
gap renders as literally nothing. The gap is the cue that reads.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(web): with no projects, open the create dialog and give the page a way in

A signed-in account with no projects landed on a page whose only path forward
was pasting a prompt into a coding agent. Now the create dialog opens itself —
with nothing to browse there is nothing else on that page to do — and the page
behind it leads with "Start a project" and a button, so closing the dialog is
not a dead end.

The dialog moves up to HubApp because three things ask for it now: the
sidebar's +, the empty state's button, and the auto-open. ProjectNav keeps only
an onNew callback; one owner beats three copies of the create handler.

Two guards on the auto-open. It fires once per mount, keyed off a ref rather
than the empty state, or closing it would immediately reopen it. And it never
fires on a read-only hub, which refuses creation server-side with a 403 —
opening a dialog that cannot succeed is worse than the page it covers.

The agent paste-prompt stays, demoted to "Or let your agent do it": it is still
the right path for someone who wants the folder connected in the same breath,
and it is the only path on a hub where this account cannot create.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 14:56:58 +09:00
..

docs.beardrive.ai

The public product documentation: CLI, sync model, self-hosting. Astro + Starlight, static output, Pagefind search.

npm install
npm run dev       # http://localhost:4321
npm run build     # -> dist/
npm run preview

Why this is a standalone site

Unlike the hub frontend (internal/webapp/static) and the cloud landing page (cloud/internal/landing/dist), this is not embedded into a Go binary:

  • Docs change far more often than the binary. Embedding them would mean a Go rebuild and redeploy to fix a typo.
  • A Pagefind search index has no business shipping inside every self-hoster's install.

It lives in the OSS repo because that is what it documents. "Edit this page" resolves to something an outside contributor can open a PR against.

Design tokens

scripts/tokens.mjs reads the @theme block in internal/webapp/frontend/src/tw.css — the source of truth for the BearDrive palette — and emits src/styles/tokens.gen.css. That file is generated, gitignored, and regenerated by npm run dev and npm run build, so the palette cannot drift. src/styles/custom.css maps Starlight's --sl-color-* variables onto it and invents no colors of its own.

(The cloud landing page can't do this — it sits in a different Go module and keeps a copy of the tokens, policed by its own check-tokens.mjs.)

Adding a page

Drop a .md file under src/content/docs/<section>/ with title and description frontmatter, then add its slug to the sidebar in astro.config.mjs. Sidebar order is explicit, not alphabetical.

Write description for every page: it is the meta description, the search result snippet, and what llms.txt shows.

llms.txt

starlight-llms-txt generates /llms.txt, /llms-small.txt, and /llms-full.txt at build time.

Convention puts llms.txt at the root domain, not a docs subdomain — so beardrive.ai/llms.txt should redirect or proxy to docs.beardrive.ai/llms.txt. That redirect lives in the cloud landing page and is the one cross-repo coordination point this split introduces.

Structure

The sidebar order in astro.config.mjs is the recommended path, and the recommended path is agent-first:

  • Start here — what it is, set up with your agent, first hour. No brew install appears in this group.
  • Working with agents — the workflows the product exists for.
  • Manual setup (optional) — the CLI route: install, set up by hand, skills and hooks in detail. Same destination, more steps; one click away, never on the critical path.
  • Use cases — job-shaped titles ("Share work across your team's agents"), persona named in the first line and in the description. These pages ROUTE: who it's for, what you get, the one setup difference, links out. The moment one starts teaching a feature, it links to the guide that owns it instead.
  • Self-hosting, Reference, Concepts — unchanged in intent.

Keep new onboarding content out of Manual. If a page teaches someone how to get started, it belongs in Start here and should say what to ask an agent, not what to type.

Deploying

Static output in dist/. Any static host works; build command npm run build, output directory dist, project root web/docs.

Redirects

The docs were reorganized around the agent-first path, so three old URLs moved:

Old New
/start/install /manual/install/
/start/quickstart /manual/setup-by-hand/
/guides/connect-an-agent /start/setup/

astro.config.mjs declares these, which in a static build emits meta-refresh pages — fine for humans, weak for search engines. Real 301s belong in the host.

Firebase Hosting (simplest static option on GCP — CDN, TLS, and custom domains included):

{
  "hosting": {
    "public": "dist",
    "ignore": ["firebase.json", "**/.*"],
    "redirects": [
      { "source": "/start/install", "destination": "/manual/install/", "type": 301 },
      { "source": "/start/quickstart", "destination": "/manual/setup-by-hand/", "type": 301 },
      { "source": "/guides/connect-an-agent", "destination": "/start/setup/", "type": 301 }
    ]
  }
}

Cloud Storage behind an external Application Load Balancer: put the rules in the URL map, which redirects before the bucket is ever reached.

gcloud compute url-maps edit docs-url-map   # pathMatchers[].pathRules[]:
#   - paths: ["/start/install"]
#     urlRedirect:
#       pathRedirect: "/manual/install/"
#       redirectResponseCode: MOVED_PERMANENTLY_DEFAULT
#       stripQuery: false

Whichever host wins, keep the Astro redirects block as well: it is the portable fallback, and it keeps local npm run preview honest.

Note that the build reads a file outside web/docs (the token source), so the host must check out the whole repository rather than just this subdirectory.