Developer Guide
WeaveForge is built using SOLID principles, emphasizing a clean, modular structure. The core domain (@weaveforge/core) is completely agnostic to UI and frameworks, while the web app (apps/web) acts as the presentation layer.
Architecture
We use a feature-sliced modular pattern:
apps/web/src/features/: The home for all UI features. Each folder represents an isolated domain (e.g.,papers,graph,logbook).apps/web/src/registry.ts: Feature module registry. CallbuildModuleRegistry(integrationConfig)for env-aware nav (e.g. hide Git when no git-read providers are enabled).apps/web/src/bootstrap.ts: Composition root — wires use-cases, integrations, and facades fromwireBackend().apps/web/src/container/facades.ts: UI entry points (getContainer().papers,.plan, …). UI must not reach into repositories directly.packages/core/: Shared interfaces, entities, and use cases.apps/web/src/backend/: Env-driven persistence + auth wiring (wireBackend()). Default: Supabase; seedocs/backend.md.docs/integrations.md: Third-party providers (Zotero, GitLab, …).docs/extensions.md: Extension seams — integrations, modules, backend, Python sync; links to plugin backlog.docs/usage-cite-and-excerpts.md: User guide for cite/excerpts/Overleaf\cite/ related papers.docs/competitive-scan.md+docs/plans/completed/competitive-scan-implementation-plan.md: product steal list + phased build plan.
Adding a New Component or Feature
Start here: docs/extensions.md for the full touch-point checklist. Below is a minimal timer example.
To add a new feature (e.g., a "Timer" module):
Create the Feature Folder Create
apps/web/src/features/timer/.Build the UI Component Create
ui/timer-screen.tsx:"use client"; import React, { useState } from "react"; export function TimerScreen() { return ( <div className="card"> <h2>Focus Timer</h2> {/* UI logic */} </div> ); }Export the Module Definition Create
module.tsin your feature folder to fulfill theFeatureModulecontract:import type { FeatureModule } from "@weaveforge/core"; export const timerModule: FeatureModule = { id: "timer", title: "Timer", navItems: [{ key: "timer", label: "Timer", path: "/timer", icon: "clock" }], routes: [{ path: "/timer", component: "timer/TimerPage" }], };Register the Module Open
apps/web/src/registry.tsand add your module toALL_MODULES:import { timerModule } from "@/features/timer/module"; const ALL_MODULES: readonly FeatureModule[] = [ // ... timerModule, ];Nav items are built by
buildModuleRegistry()— no edits totabbar.tsxper feature. If the module belongs in a grouped sub-nav (Library, Plan, …), add its key tobuildNavGroupsFromItems()inregistry.ts. Createapps/web/src/app/<path>/page.tsxmanually (Next.js App Router — not auto-wired fromFeatureModule.routesyet).Wire domain logic Add use-cases in
packages/core, wire inbootstrap.ts, expose via a facade infacades.ts. Seeextensions.md§4.
Adding or swapping an integration
Third-party services (Zotero, GitLab, Mattermost, Semantic Scholar, …) are wired through env-selected providers at the composition root. UI reads descriptors for Settings forms and calls facades for behavior.
Full guide: docs/integrations.md
Quick summary:
- Implement the core port (
IBibliographyIntegration,ICitationSource, etc.). - Add provider adapter under
apps/web/src/integrations/providers/<name>/. - Register via integration manifests (
integrations/manifests/) andconfig.ts; wire inwire-integrations.ts/wire-citations.ts. - Set
NEXT_PUBLIC_*_PROVIDERenv var for your deployment.
Post-merge review checklist (SOLID / DRY)
When landing sharing, library, or org onboarding work, confirm:
| Area | Rule |
|---|---|
| Share matching | Use shareCoversResource / shareAllowsComment from @weaveforge/core in use-cases — never in UI components. |
| Library pins | Writes go through PinSharedResourceUseCase; repos must pass runLibraryPinRepositoryContract. |
| Screen data | Multi-repo orchestration lives in Load*ScreenUseCase classes wired in bootstrap.ts, not in React screens. Pin merge uses mergePinnedScreenData from @weaveforge/core. |
| Pinned owner labels | Use loadPinnedOwnerNames() — do not duplicate buildMemberNameMap loops in screens. |
| Duplicate copy | Papers: DuplicateSharedPaperUseCase (core). Vault pages: DuplicateSharedVaultPageUseCase (web, asset re-upload). Both require an active share grant. |
| Org API routes | Shared helpers in apps/web/src/app/api/org/_shared.ts; code preview requires auth. |
| Standalone onboarding | Client calls complete_org_setup() RPC; /api/org/standalone is the JWT fallback only. |
| Module barrels | Import web library adapter via @/features/library, not deep infrastructure paths. |
SOLID boundaries (enforced in CI)
npm run check:solid runs in the build-and-test CI job on every PR to main. The script fails on:
- Supabase imports under
features/**/ui/ - Cross-feature imports from another feature's
ui/folder (use the feature's public barrel, e.g.@/features/sharing) - UI calling
getContainer().*Repositorydirectly (use facades)
npm run check:dry runs in the same job. It fails when UI screens duplicate patterns we centralised: share matching, pin merge, or pinned owner label loops.
UI talks to getContainer().<feature> facades only; repositories stay behind use-cases in bootstrap.ts.
Merging to main
main is protected. Before a PR can merge:
| Required check | What it runs |
|---|---|
| build-and-test | npm run build:core, core + web tests, Supabase contract tests, typecheck, check:solid, check:dry, lint, Next.js build |
| python-sdk | ruff, mypy, pytest |
Rules: pull request required — direct pushes to main are blocked, including for repo admins. Branch must be up to date with main; required checks must pass; no force-push or branch deletion. Approving review count is 0 (solo maintainer can merge their own PR after CI).
Releases: Git tags / PyPI = Python SDK only. See docs/release.md.
Repo admins can re-apply protection: .github/scripts/apply-main-branch-protection.sh (needs gh with admin scope).
Because data is heavily scoped to "Projects", we centralize project context but isolate feature data:
Local State: Use
useStatefor simple UI toggles.Context Providers: Feature-wide state is provided by Contexts. E.g.,
project-provider.tsxfetches and caches the active project, providingcurrentandprojectsdownstream without prop-drilling.Data Fetching: UI calls
getContainer().<feature>facades; facades delegate to use-cases wired inbootstrap.ts. Do not add ad-hocfetchin components except for Next.js API routes consumed by infrastructure adapters.Caching & Optimistic Updates: To ensure the UI feels instant:
- Always perform optimistic updates before the network request finishes.
- Maintain local arrays in state (
setPapers(prev => [...prev, newPaper])). - Re-sync from the DB on focus or interval if multi-device sync is critical.
If you need a more robust caching solution for a new feature, you can implement a standard cache invalidation pattern or introduce lightweight SWR hooks inside your feature, maintaining the rule that features should not bleed their dependencies into the global scope.
Reusable UI Components
Use the components in apps/web/src/components/ (like <Select>, <Modal>, etc) to ensure the UI remains visually consistent with the docs/themes.md design system. Avoid writing ad-hoc inline styles unless dealing with absolute edge-case positioning.