Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
dbec7b3
docs(teams): update roles, member lifecycle, and project visibility
phernandez Jul 2, 2026
c20adcc
docs(search): document the categories observation filter on search_notes
phernandez Jul 2, 2026
9702fa4
docs: adopt docs-check tooling and new pages from docs-overhaul branch
phernandez Jul 2, 2026
f457e66
docs(mcp): source-backed MCP tools reference update
phernandez Jul 2, 2026
a3a8939
docs(teams): add Partners (MSP) page
phernandez Jul 2, 2026
e8d38d1
docs: replace marketing homepage with a docs-style Welcome page
phernandez Jul 2, 2026
3d1f58c
docs: serve Welcome as a docs page, redirect root to /welcome
phernandez Jul 2, 2026
60b5553
docs: keep what-is-basic-memory general, drop implementation details
phernandez Jul 2, 2026
60f7473
docs: better mermaid diagrams — site-matched theme, compact layouts
phernandez Jul 2, 2026
9e14470
docs: move Getting Started into the Local section
phernandez Jul 2, 2026
2cc3561
docs: remove why-basic-memory page
phernandez Jul 2, 2026
49ec1ec
docs: fold release notes into a single Changelog page
phernandez Jul 2, 2026
8b50f88
docs: remove the v0.19 migration guide
phernandez Jul 2, 2026
10496cf
docs: remove bisync everywhere, drop edit-locally page
phernandez Jul 2, 2026
b7989f4
docs: fix commands, values, and claims that fail against v0.22.1
phernandez Jul 2, 2026
c3a3c2e
docs: drop hard-coded counts and version pins that rot
phernandez Jul 2, 2026
10af843
docs: retire "bidirectional sync" phrasing
phernandez Jul 2, 2026
33f63dd
docs: adopt overhaul branch's Docker page, fix dead MCP spec link
phernandez Jul 2, 2026
11c623b
docs: remove unshipped features and stale pages, surface shipped ones
phernandez Jul 2, 2026
dff521a
docs: update cloud quickstart and web app for the onboarding overhaul
phernandez Jul 2, 2026
bd20480
docs: dedupe concepts and reference pages, fix worked example
phernandez Jul 2, 2026
d9668ad
docs: add todo.md for screenshots and manual verification
phernandez Jul 2, 2026
e8b2c17
docs: rewrite AI assistant guide, move Contact Support to right TOC
phernandez Jul 2, 2026
60201b8
docs: align AI-assistant-guide card descriptions with the new page
phernandez Jul 2, 2026
e2a5295
docs(skills): point at the basic-memory monorepo skills directory
phernandez Jul 2, 2026
f013c47
docs(skills): add example prompts for invoking skills
phernandez Jul 2, 2026
776623e
docs(skills): use slash-command style in skill invocation examples
phernandez Jul 2, 2026
599781c
docs(skills): name Claude Code and Codex for slash-command invocation
phernandez Jul 2, 2026
b47f5d6
docs(skills): link Node.js install for users without npx
phernandez Jul 2, 2026
46d9aee
docs: add full-site review findings for manual triage
phernandez Jul 2, 2026
0b54b49
docs: apply review fixes for Welcome and Start Here
phernandez Jul 2, 2026
b177734
docs: apply review fixes for What's New section
phernandez Jul 2, 2026
3f5ae4a
docs: teams and one-memory-for-all-agents framing on what-is page
phernandez Jul 3, 2026
dbb69a8
docs: name OpenClaw and Hermes in the what-is intro
phernandez Jul 3, 2026
9828107
docs: keep ChatGPT in the what-is intro tool list
phernandez Jul 3, 2026
ba33a57
docs: capture signup attribution from the docs site
phernandez Jul 3, 2026
2ee1ea0
docs: tag every signup and subscribe CTA with docs attribution
phernandez Jul 3, 2026
5b65022
docs: one home for the Claude cloud walkthrough
phernandez Jul 3, 2026
2cf736a
docs: refresh ChatGPT setup-path todo entry
phernandez Jul 3, 2026
5a1d326
docs: add invite-your-team step to the cloud quickstart
phernandez Jul 3, 2026
2bf4906
docs: web-app guide callout in quickstart view-notes step
phernandez Jul 3, 2026
5645c32
docs: same compress-and-link treatment for the local quickstart
phernandez Jul 3, 2026
f682c54
docs(teams): split the Teams guide into focused pages
phernandez Jul 3, 2026
67d59bd
docs: finish one-memory framing on the Teams announcement
phernandez Jul 3, 2026
cd72d77
docs(plugins): point Hermes and OpenClaw pages at the monorepo
phernandez Jul 3, 2026
65786a2
docs(cloud): fix cloud-guide commands and trim its CLI walkthrough
phernandez Jul 3, 2026
feb6219
docs(cloud): surface the Settings -> Import Project ZIP upload path
phernandez Jul 3, 2026
f0bcb93
docs(cloud): web app page matches the shipped UI
phernandez Jul 3, 2026
21f1150
docs(cloud): cloud-sync page matches actual CLI behavior
phernandez Jul 3, 2026
f836772
docs(cloud): routing page drops the global-cloud-mode model
phernandez Jul 3, 2026
5dc0405
docs(cloud): snapshots and restore pages match the real CLI and service
phernandez Jul 3, 2026
7ffadf9
docs(cloud): api-keys page matches the app and client configs that work
phernandez Jul 3, 2026
709dc99
docs(cloud): shared-notes, file-history, and cloud-cli corrections
phernandez Jul 3, 2026
198aabf
docs(cloud): rewrite themes page around the shipped preset system
phernandez Jul 3, 2026
d7ab60c
docs(cloud): split the web app page into overview, Editor, and AI Col…
phernandez Jul 3, 2026
ec031e2
docs(cloud): rename Editor page to Note Editor
phernandez Jul 3, 2026
9c95239
docs: add a subtle border to theme-image screenshots
phernandez Jul 3, 2026
9f55565
docs: darken the theme-image screenshot border
phernandez Jul 3, 2026
64d462b
docs(cloud): move Routing to the bottom of the cloud nav
phernandez Jul 3, 2026
ce1bb95
docs(cloud): clarify Shared Notes are public shares
phernandez Jul 3, 2026
493d68b
docs(cloud): recovery page ends with a contact-support path
phernandez Jul 3, 2026
e916913
docs(cloud): recovery contact uses support@ and the in-app feedback b…
phernandez Jul 3, 2026
5fe0039
docs: user support goes to support@basicmemory.com
phernandez Jul 3, 2026
3219d5b
docs(partners): promote Partners to its own section for the MSP audience
phernandez Jul 3, 2026
c936eb0
docs: update CLAUDE.md directory tree for current sections
phernandez Jul 3, 2026
59763c9
docs(local): apply review fixes across the Local section
phernandez Jul 3, 2026
d34a12d
docs(local): link cli-basics schema workflows to the Schema System page
phernandez Jul 3, 2026
82e84ae
docs(concepts): apply all 31 review fixes across the Concepts section
phernandez Jul 3, 2026
01df05d
docs: move memory URL resolution details to the local user guide
phernandez Jul 3, 2026
8bd3e2f
docs: canvas moves to the Local section as a local-only tool
phernandez Jul 3, 2026
4f9019e
docs(concepts): best-of-both tip on the built-in memory page
phernandez Jul 3, 2026
ebc85e2
docs(search): document searching observations by category everywhere
phernandez Jul 3, 2026
1a82dd0
docs: zero-pad section numbering so nav sorts correctly
phernandez Jul 3, 2026
ee224fc
docs: check-docs whats-new exemption follows the padded dir name
phernandez Jul 3, 2026
d6275ec
docs(integrations): apply remaining review fixes across all clients
phernandez Jul 3, 2026
28e0f29
docs(how-to): schemas validate observations, not sections — plus hone…
phernandez Jul 3, 2026
c8f18d1
docs(how-to): project setup works on cloud or local
phernandez Jul 3, 2026
e11cc1f
docs(how-to): recommend cloud as the home for project docs
phernandez Jul 3, 2026
cde9a80
docs(how-to): writing page gets cloud-neutral setup and an anti-slop …
phernandez Jul 3, 2026
9eba275
docs: add LinkedIn to socials and the Contact Us list
phernandez Jul 3, 2026
8952c31
docs(reference): apply all review fixes across the Reference section
phernandez Jul 3, 2026
29f40b4
docs: cross-link the routing-rule tip and the AI assistant guide
phernandez Jul 3, 2026
c4184fb
docs: Postgres on the Docker page is a supported backend, not a dev toy
phernandez Jul 3, 2026
75e044c
docs: close out the cross-cutting review findings
phernandez Jul 3, 2026
624bc2d
docs: refresh stale todo entries after the review pass
phernandez Jul 3, 2026
089d260
docs: screenshot audit — delete stale unreferenced captures
phernandez Jul 3, 2026
2b33a87
docs: corrections from live production verification
phernandez Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/docs-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Docs checks

on:
pull_request:
push:
branches: [main]

jobs:
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run check
29 changes: 16 additions & 13 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,18 @@ just deploy production # Deploy to production environment
│ ├── app.config.ts # Docus configuration
│ └── components/ # Custom Vue components
├── content/ # Documentation pages (Markdown)
│ ├── 1.start-here/ # Getting started guides
│ ├── 2.whats-new/ # Release notes
│ ├── 3.cloud/ # Cloud documentation
│ ├── 4.local/ # Local installation
│ ├── 5.concepts/ # Core concepts
│ ├── 6.integrations/ # Integration guides
│ ├── 7.how-to/ # How-to guides
│ └── 8.reference/ # Technical reference
│ ├── 0.welcome.md # Landing page (/ redirects to /welcome)
│ ├── 01.start-here/ # Getting started guides
│ ├── 02.whats-new/ # Announcements + changelog
│ ├── 03.cloud/ # Cloud documentation
│ ├── 04.teams/ # Teams documentation
│ ├── 05.partners/ # MSP partner program
│ ├── 06.local/ # Local installation
│ ├── 07.concepts/ # Core concepts
│ ├── 08.integrations/ # Integration guides
│ ├── 09.how-to/ # How-to guides
│ └── 10.reference/ # Technical reference
# Nav order sorts numeric prefixes lexically — keep them zero-padded
├── public/ # Static assets
├── server/ # Server routes (API)
├── nuxt.config.ts # Nuxt configuration
Expand Down Expand Up @@ -174,11 +178,10 @@ pip install basic-memory

When updating docs for a new Basic Memory release, update all of the following:

1. **Homepage version badge** — `content/index.md`: update the version text (e.g., `v0.22 →`) and the `to:` link to point to the new release notes page
2. **Release notes page** — new minor/major: replace the previous version's page — rename `content/2.whats-new/1.v<OLD>.md` to `1.v<NEW>.md` and rewrite it for the new release. Only the latest release gets a dedicated page (the changelog covers older releases), so the left nav must never show two version entries. Update any inbound links to the old release URL: latest-release cards point to the new page; references to version-specific behavior point to the GitHub release tag. Patch release: append a short note to the current version's page instead
3. **Changelog** — the `*.changelog.md` page under `content/2.whats-new/` auto-fetches from the GitHub releases API, no manual update needed
4. **Feature docs** — if the release adds user-facing features, update the relevant guide and reference pages (`content/3.cloud/`, `content/9.reference/`, etc.)
5. **Deploy** — push to main auto-deploys to development; production requires manual workflow dispatch via GitHub Actions
1. **Welcome page What's New callout** — `content/0.welcome.md`: update the version text (e.g., `v0.22`) and the headline; the link stays `/whats-new/changelog`
2. **Changelog page** — `content/2.whats-new/1.changelog.md`: add a section for the new minor/major version at the top, linking the GitHub release tag(s). Patch release: add a short bullet list under its minor version's section instead. The `::github-releases` block at the bottom auto-fetches full release notes from the GitHub API — no manual update needed there. There are no per-version pages — deep links to version-specific behavior point at GitHub release tags
3. **Feature docs** — if the release adds user-facing features, update the relevant guide and reference pages (`content/3.cloud/`, `content/9.reference/`, etc.)
4. **Deploy** — push to main auto-deploys to development; production requires manual workflow dispatch via GitHub Actions

## Documentation Status & Priorities

Expand Down
7 changes: 5 additions & 2 deletions app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,17 @@ export default defineAppConfig({
x: 'https://x.com/basic_memory',
discord: 'https://discord.gg/tyvKNccgqN',
reddit: 'https://www.reddit.com/r/basicmemory',
linkedin: 'https://www.linkedin.com/company/basicmemory/',
},
toc: {
bottom: {
title: 'Community',
title: 'Contact Us',
links: [
{ label: 'Contact Support', to: '/reference/contact-support' },
{ label: 'Discord', to: 'https://discord.gg/tyvKNccgqN' },
{ label: 'GitHub', to: 'https://github.com/basicmachines-co/basic-memory' },
{ label: 'X / Twitter', to: 'https://x.com/basic_memory' },
{ label: 'Discord', to: 'https://discord.gg/tyvKNccgqN' },
{ label: 'LinkedIn', to: 'https://www.linkedin.com/company/basicmemory/' },
{ label: 'Reddit', to: 'https://www.reddit.com/r/basicmemory' },
],
},
Expand Down
28 changes: 22 additions & 6 deletions app/components/content/Mermaid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,28 @@ async function renderMermaid() {
// Dynamic import to keep bundle size down when mermaid isn't used
const { renderMermaid: render } = await import('beautiful-mermaid')

// Use different colors for light/dark mode
const svg = await render(props.code.trim(), {
bg: isDark.value ? '#1e293b' : '#ffffff',
fg: isDark.value ? '#e2e8f0' : '#18181b',
transparent: true,
})
// Enriched theme matching the site palette (orange primary, stone neutral)
const svg = await render(props.code.trim(), isDark.value
? {
bg: '#1c1917', // stone-900
fg: '#e7e5e4', // stone-200
accent: '#f97316', // orange-500 — arrow heads, highlights
line: '#78716c', // stone-500 — connectors
muted: '#a8a29e', // stone-400 — edge labels, secondary text
surface: '#292524', // stone-800 — node fill
border: '#57534e', // stone-600 — node stroke
transparent: true,
}
: {
bg: '#ffffff',
fg: '#292524', // stone-800
accent: '#ea580c', // orange-600
line: '#a8a29e', // stone-400
muted: '#78716c', // stone-500
surface: '#fafaf9', // stone-50
border: '#d6d3d1', // stone-300
transparent: true,
})

svgContent.value = svg
} catch (e) {
Expand Down
4 changes: 2 additions & 2 deletions app/components/content/ThemeImage.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<span class="block">
<img :src="light" :alt="alt" class="block dark:hidden rounded-md" />
<img :src="dark" :alt="alt" class="hidden dark:block rounded-md" />
<img :src="light" :alt="alt" class="block dark:hidden rounded-md border border-neutral-300 dark:border-neutral-700" />
<img :src="dark" :alt="alt" class="hidden dark:block rounded-md border border-neutral-300 dark:border-neutral-700" />
</span>
</template>

Expand Down
259 changes: 259 additions & 0 deletions app/plugins/signup-attribution.client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
/**
* Signup attribution capture — port of basicmemory.com's
* src/lib/signup-attribution.ts. Writes the shared
* `bmc_signup_attribution_v1` cookie on `.basicmemory.com` so signups at
* app.basicmemory.com can be attributed to a docs first touch. Keep the
* cookie name, shape, and semantics in sync with the marketing site.
*/

const COOKIE_NAME = 'bmc_signup_attribution_v1'
const COOKIE_DAYS = 90
const SELF_DOMAINS = ['basicmemory.com', 'app.basicmemory.com', 'localhost']
const PASSIVE_CHANNELS = [
'unknown',
'reddit',
'google',
'facebook',
'microsoft',
'x_twitter',
'youtube',
'referral',
] as const

type PassiveChannel = (typeof PASSIVE_CHANNELS)[number]

interface PassiveAttribution {
event_id: string
captured_at: string
landing_url: string
landing_path: string
referrer: string | null
referrer_host: string | null
utm_source: string | null
utm_medium: string | null
utm_campaign: string | null
utm_term: string | null
utm_content: string | null
gclid: string | null
fbclid: string | null
msclkid: string | null
rdt_cid: string | null
rdt_click_id: string | null
passive_channel: PassiveChannel
}

interface SignupAttributionState {
first_touch: PassiveAttribution
latest_touch: PassiveAttribution
}

function safeUrl(raw: string): URL | null {
try {
return new URL(raw)
} catch {
return null
}
}

function normalizeValue(value: string | null | undefined): string | null {
if (!value) return null
const normalized = value.trim().toLowerCase()
return normalized.length > 0 ? normalized : null
}

function cookieValue(name: string): string | null {
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const match = document.cookie.match(new RegExp(`(?:^|; )${escapedName}=([^;]*)`))
if (!match || !match[1]) return null

try {
return decodeURIComponent(match[1])
} catch {
return match[1]
}
}

function isPassiveChannel(value: unknown): value is PassiveChannel {
return typeof value === 'string' && PASSIVE_CHANNELS.includes(value as PassiveChannel)
}

function isTouch(value: unknown): value is PassiveAttribution {
if (!value || typeof value !== 'object') return false
const candidate = value as Partial<PassiveAttribution>
return typeof candidate.event_id === 'string'
&& typeof candidate.captured_at === 'string'
&& typeof candidate.landing_url === 'string'
&& typeof candidate.landing_path === 'string'
&& isPassiveChannel(candidate.passive_channel)
}

function isAttributionState(value: unknown): value is SignupAttributionState {
if (!value || typeof value !== 'object') return false
const candidate = value as Partial<SignupAttributionState>
return isTouch(candidate.first_touch) && isTouch(candidate.latest_touch)
}

function readAttributionCookie(): SignupAttributionState | null {
const rawValue = cookieValue(COOKIE_NAME)
if (!rawValue) return null

try {
const parsed = JSON.parse(rawValue)
return isAttributionState(parsed) ? parsed : null
} catch {
return null
}
}

function isSelfDomain(hostname: string): boolean {
return SELF_DOMAINS.some(domain => hostname === domain || hostname.endsWith(`.${domain}`))
}

function referrerHost(): string | null {
const referrer = normalizeValue(document.referrer)
if (!referrer) return null

const parsedReferrer = safeUrl(referrer)
if (!parsedReferrer) return null

const hostname = parsedReferrer.hostname.trim().toLowerCase()
if (!hostname || isSelfDomain(hostname)) return null
return hostname
}

function sourceToPassiveChannel(source: string | null): PassiveChannel | null {
if (!source) return null

if (source.includes('reddit') || source === 'rdt') return 'reddit'
if (source.includes('google')) return 'google'
if (source.includes('facebook') || source === 'fb' || source.includes('instagram')) {
return 'facebook'
}
if (source.includes('bing') || source.includes('microsoft')) return 'microsoft'
if (source.includes('twitter') || source === 'x') return 'x_twitter'
if (source.includes('youtube') || source.includes('youtu')) return 'youtube'

return null
}

function passiveChannel(values: {
utmSource: string | null
rdtCid: string | null
rdtClickId: string | null
gclid: string | null
fbclid: string | null
msclkid: string | null
referrerHost: string | null
}): PassiveChannel {
const utmChannel = sourceToPassiveChannel(values.utmSource)
if (utmChannel) return utmChannel
if (values.utmSource) return 'referral'
if (values.rdtCid || values.rdtClickId) return 'reddit'
if (values.gclid) return 'google'
if (values.fbclid) return 'facebook'
if (values.msclkid) return 'microsoft'
if (values.referrerHost) return sourceToPassiveChannel(values.referrerHost) ?? 'referral'
return 'unknown'
}

function eventId(): string {
if (window.crypto && typeof window.crypto.randomUUID === 'function') {
return window.crypto.randomUUID()
}
return `evt_${Date.now()}_${Math.random().toString(36).slice(2, 12)}`
}

function buildTouch(): PassiveAttribution {
const url = new URL(window.location.href)
const utmSource = normalizeValue(url.searchParams.get('utm_source'))
const rdtCid = normalizeValue(url.searchParams.get('rdt_cid'))
const rdtClickId = normalizeValue(cookieValue('_rdt_click_id'))
const gclid = normalizeValue(url.searchParams.get('gclid'))
const fbclid = normalizeValue(url.searchParams.get('fbclid'))
const msclkid = normalizeValue(url.searchParams.get('msclkid'))
const host = referrerHost()

return {
event_id: eventId(),
captured_at: new Date().toISOString(),
landing_url: `${url.origin}${url.pathname}`,
landing_path: url.pathname,
referrer: host,
referrer_host: host,
utm_source: utmSource,
utm_medium: normalizeValue(url.searchParams.get('utm_medium')),
utm_campaign: normalizeValue(url.searchParams.get('utm_campaign')),
utm_term: normalizeValue(url.searchParams.get('utm_term')),
utm_content: normalizeValue(url.searchParams.get('utm_content')),
gclid,
fbclid,
msclkid,
rdt_cid: rdtCid,
rdt_click_id: rdtClickId,
passive_channel: passiveChannel({
utmSource,
rdtCid,
rdtClickId,
gclid,
fbclid,
msclkid,
referrerHost: host,
}),
}
}

function hasSignal(touch: PassiveAttribution): boolean {
return touch.passive_channel !== 'unknown'
|| !!touch.referrer_host
|| !!touch.utm_source
|| !!touch.utm_medium
|| !!touch.utm_campaign
|| !!touch.utm_term
|| !!touch.utm_content
|| !!touch.gclid
|| !!touch.fbclid
|| !!touch.msclkid
|| !!touch.rdt_cid
|| !!touch.rdt_click_id
}

function writeAttributionCookie(state: SignupAttributionState): void {
const expires = new Date()
expires.setTime(expires.getTime() + COOKIE_DAYS * 24 * 60 * 60 * 1000)

let cookie = `${COOKIE_NAME}=${encodeURIComponent(JSON.stringify(state))}`
cookie += `;expires=${expires.toUTCString()};path=/;SameSite=Lax`

const hostname = window.location.hostname.toLowerCase()
if (hostname === 'basicmemory.com' || hostname.endsWith('.basicmemory.com')) {
cookie += ';domain=.basicmemory.com'
}
if (window.location.protocol === 'https:') {
cookie += ';Secure'
}

document.cookie = cookie
}

function captureSignupAttribution(): void {
const current = readAttributionCookie()
const captured = buildTouch()
const state = current
? {
first_touch: current.first_touch,
latest_touch: hasSignal(captured) ? captured : current.latest_touch,
}
: {
first_touch: captured,
latest_touch: captured,
}

writeAttributionCookie(state)
}

export default defineNuxtPlugin(() => {
captureSignupAttribution()
useRouter().afterEach(() => {
captureSignupAttribution()
})
})
Loading
Loading