This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
mo is a CLI tool that opens Markdown files in a browser with live-reload. It runs a Go HTTP server that embeds a React SPA as a single binary. The Go module is github.com/k1LoW/mo.
Requires Go and pnpm. Node.js version is managed via pnpm.executionEnv.nodeVersion in internal/frontend/package.json.
# Full build (frontend + Go binary, with ldflags)
make build
# Dev: build frontend then run with args
make dev ARGS="testdata/basic.md"
# Dev with tab groups (-t can only specify one group per invocation)
make dev ARGS="-t design testdata/basic.md"
# Frontend code generation only (called by make build/dev via go generate)
make generate
# Run all tests (frontend + Go)
make test
# Run a single frontend test
cd internal/frontend && pnpm test src/utils/buildTree.test.ts
# Run Go tests only
go test ./...
# Run linters (golangci-lint + gostyle)
make lint
# CI target (install dev deps + generate + test)
make ci--port/-p— Server port (default: 6275)--target/-t— Tab group name (default:"default")--open— Always open browser--no-open— Never open browser--watch/-w— Glob pattern to watch for matching files (repeatable)--unwatch— Remove a watched glob pattern (repeatable)--status— Show status of all running mo servers--shutdown— Shut down the running mo server--foreground— Run mo server in foreground (do not background)
Go backend + embedded React SPA, single binary.
cmd/root.go— CLI entry point (Cobra). Handles single-instance detection: if a server is already running on the port, adds files via HTTP API instead of starting a new server.internal/server/server.go— HTTP server, state management (mutex-guarded), SSE for live-reload, file watcher (fsnotify). All API routes use/_/prefix to avoid collision with SPA route paths (group names).internal/static/static.go—go:generateruns the frontend build, thengo:embedembeds the output frominternal/static/dist/.internal/frontend/— Vite + React 19 + TypeScript + Tailwind CSS v4 SPA. Build output goes tointernal/static/dist/(configured invite.config.ts).version/version.go— Version info, updated by tagpr on release. Build embeds revision via ldflags.
- Package manager: pnpm (version specified in
internal/frontend/package.jsonpackageManagerfield) - Markdown rendering:
react-markdown+remark-gfm+rehype-raw+rehype-slug(heading IDs) +@shikijs/rehype(syntax highlighting) +mermaid(diagram rendering) - SPA routing via
window.location.pathname(no router library) - Key components:
App.tsx(routing/state),Sidebar.tsx(file list with flat/tree view, resizable, drag-and-drop reorder),TreeView.tsx(tree view with collapsible directories),MarkdownViewer.tsx(rendering + raw view toggle),TocPanel.tsx(table of contents, resizable),GroupDropdown.tsx(group switcher),FileContextMenu.tsx(shared kebab menu for file operations) - Custom hooks:
useSSE.ts(SSE subscription with auto-reconnect),useApi.ts(typed API fetch wrappers),useActiveHeading.ts(scroll-based active heading tracking via IntersectionObserver) - Utilities:
buildTree.ts(converts flat file list to hierarchical tree with common prefix removal and single-child directory collapsing) - Theme: GitHub-style light/dark via CSS custom properties (
--color-gh-*) instyles/app.css, toggled bydata-themeattribute on<html>. UI components use Tailwind classes likebg-gh-bg-sidebar,text-gh-text-secondary, etc. - Toggle button pattern:
RawToggle.tsxandTocToggle.tsxfollow the same style (bg-transparent border border-gh-border rounded-md p-1.5 text-gh-text-secondary). Header buttons (ViewModeToggle,ThemeToggle, sidebar toggle) usetext-gh-header-textinstead. New buttons should match the appropriate variant.
- Single instance: CLI probes
/_/api/statuson the target port viaprobeServer(). If already running, pushes files viaPOST /_/api/filesand exits. - File IDs: Files get sequential integer IDs server-side. The frontend primarily references files by ID. Absolute paths are available via
FileEntry.pathfor display (e.g., tooltip, tree view). - Tab groups: Files are organized into named groups. Group name maps to the URL path (e.g.,
/design). Default group name is"default". - Live-reload via SSE: fsnotify watches files;
file-changedevents trigger frontend to re-fetch content by file ID. - Sidebar view modes: Flat (default, with drag-and-drop reorder via dnd-kit) and tree (hierarchical directory view). View mode is persisted per-group in localStorage. Collapsed directory state is managed inside
TreeViewand also persisted per-group. - Resizable panels: Both
Sidebar.tsx(left) andTocPanel.tsx(right) use the same drag-to-resize pattern with localStorage persistence. Left sidebar usese.clientX, right panel useswindow.innerWidth - e.clientX. - Toolbar buttons in content area: The toolbar column (ToC + Raw toggles) lives inside
MarkdownViewer.tsx, positioned withshrink-0 flex flex-col gap-2 -mr-4 -mt-4to align with the header. - Glob pattern watching:
--watchregisters glob patterns that are expanded to matching files and monitored for new files via fsnotify directory watches. Patterns are stored with reference-counted directory watches (watchedDirs map[string]int).--unwatchremoves patterns and decrements watch ref counts. Groups persist as long as they have files or patterns. - localStorage conventions: All keys use
mo-prefix (e.g.,mo-sidebar-width,mo-sidebar-viewmode,mo-sidebar-tree-collapsed,mo-theme). Read patterns usetry/catcharoundJSON.parsewith fallback defaults.
All internal endpoints use /_/api/ prefix and SSE uses /_/events. The /_/ prefix avoids collisions with user-facing group name routes.
Key endpoints:
GET /_/api/groups— List all groups with filesPOST /_/api/files— Add fileDELETE /_/api/files/{id}— Remove fileGET /_/api/files/{id}/content— File content (markdown)PUT /_/api/files/{id}/group— Move file to another groupPUT /_/api/reorder— Reorder files in a group (group name in body)POST /_/api/files/open— Open relative file linkPOST /_/api/patterns— Add glob watch patternDELETE /_/api/patterns— Remove glob watch patternGET /_/api/status— Server status (version, pid, groups with patterns)GET /_/events— SSE (event types:update,file-changed,restart)
- CI: golangci-lint (via reviewdog), gostyle,
make ci(test + coverage), octocov - Release: tagpr for automated tagging, goreleaser for cross-platform builds. The
go generatestep (frontend build) runs in goreleaser'sbefore.hooks. - License check: Trivy scans for license issues
- CI requires pnpm setup (
pnpm/action-setup) before any Go build step becausego generatetriggers the frontend build.