Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
61 changes: 61 additions & 0 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Docs
on:
push:
paths:
- 'docs/**'
- '.github/workflows/docs.yaml'
branches:
- develop
pull_request:
paths:
- 'docs/**'
- '.github/workflows/docs.yaml'
branches:
- develop

permissions:
contents: read

jobs:
build:
name: Build
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./docs
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
package_json_file: 'docs/package.json'
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: './docs/package.json'
cache: 'pnpm'
cache-dependency-path: './docs/pnpm-lock.yaml'
- run: pnpm install --frozen-lockfile
- run: pnpm build
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
if: ${{ github.ref == 'refs/heads/develop' }}
with:
path: docs/build

deploy:
name: Deploy to GitHub Pages
if: ${{ github.ref == 'refs/heads/develop' }}
needs: build
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ Key documentation for this project:
- `README.md` - Project overview and setup
- `AGENTS.md` - You are here! Agent instructions
- `.github/AGENTS.md` - **CI/CD and deployment guide** (workflows, K8s, Docker)
- `docs/DEVELOPER-win.md` - Windows development setup
- `docs/DEVELOPER-linux.md` - Linux development setup
- `docs/DEVELOPER-osx.md` - macOS development setup
- `docs/technical/development/setup-windows.md` - Windows development setup
- `docs/technical/development/setup-linux.md` - Linux development setup
- `docs/technical/development/setup-macos.md` - macOS development setup
- `backend/README.md` - Backend architecture
- `backend/AGENTS.md` - General backend guidelines
- `backend/LexBoxApi/AGENTS.md` - API & GraphQL specific rules
Expand Down
69 changes: 69 additions & 0 deletions DOCS-PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Documentation plan

Plan and findings behind the `docs/` site on this branch. Written 2026-07-29; remove or move this file once the approach is settled.

## The problem

There is no documentation home. The real technical content (sync architecture, integrations, CI/CD) lives in nine `AGENTS.md` files and the root README — written for coding agents, invisible to anyone who won't browse the repo. User documentation doesn't exist here at all; the app links out to the classic FieldWorks help site and the SIL community forum. Colleagues asking "do you have technical docs / diagrams?" currently get pointed at agent-instruction files.

## What SIL uses (surveyed 2026-07)

| Project | Tooling | i18n |
|---|---|---|
| Bloom (docs.bloomlibrary.org) | Docusaurus + Algolia DocSearch + Ask AI chat | Crowdin scaffolded |
| Scripture Forge (help.scriptureforge.org) | Docusaurus | 6 locales via Crowdin |
| Paratext (manual.paratext.org) | Docusaurus | 5 locales |
| The Combine | MkDocs Material (chosen for offline in-app bundling) | 4 locales |
| Keyman | bespoke PHP (legacy) | — |
| FieldWorks | WordPress + GitHub wikis (stale since 2020–22) | — |

Docusaurus is the SIL convention: markdown in git, Crowdin for translation, static hosting. Bloom's site AI chat is Algolia **Ask AI** — a few lines in `docusaurus.config` pointing at an Algolia index of the rendered markdown site, so choosing Docusaurus gets the AI-chat path for free. GitHub wikis are where SIL dev docs go stale.

## Decisions

- **One Docusaurus site, two doc sections** (`/user-guide/`, `/technical/`): one pipeline, one search index, one AI-chat corpus, but the user guide stays plain-language and translatable while technical docs stay English-first.
- **Source lives in this monorepo** (unlike Bloom/SF/Paratext, which use separate repos): docs next to code is what makes "the AI updates the docs in the same PR as the code change" real, and reviewers see doc drift in the diff.
- **Terminology follows the shipped UI**: FieldWorks Lite, FieldWorks Classic, Lexbox, Send/Receive. CRDT/Harmony/Mercurial/FwHeadless are technical-section vocabulary only.
- **Seed content is adapted from already-reviewed sources** (README, AGENTS.md files, DEVELOPER-*.md), not newly authored prose. `AGENTS.md` files are untouched in this PR; de-duplicating them to link into the site is a follow-up.
- **Deployment**: GitHub Pages workflow included (build on PR, deploy on develop push). Target URL and DNS (`docs.lexbox.org`?) are a team decision.
- Later, in rough order: Crowdin wiring for the user guide (copy Scripture Forge's setup), Algolia DocSearch + Ask AI (copy Bloom's config), screenshots in the user guide, moving `AGENTS.md` architecture content into the site and linking back.
- **Relationship to FieldWorks Classic docs**: sibling products, sibling doc sites — link, don't share. The app already deep-links per-field into the classic FieldWorks help site (`FieldHelpIcon`), and these docs do the same: user-guide pages link out to classic help/product pages wherever the reader needs classic-side steps (Send/Receive, installing FieldWorks), and never duplicate classic documentation. No shared tooling required; "FieldWorks Classic" is our UI's name for it, used consistently.
- **Component framework**: the site is Docusaurus, so interactive doc components are React — accepted, not preferred (products are Svelte). Mitigation: everything maintainers routinely edit lives in framework-neutral files (`syncScenarios.ts`, plain CSS); only the rendering file is React. Revisit trigger: if product-like interactive components accumulate here, either compile Svelte components to web components for MDX (loses SSR, adds a Svelte build step) or move the site to Astro Starlight (native Svelte islands, at the cost of the shared-SIL-tooling argument).

## The sync explainer

The hardest thing to explain is FieldWorks Lite ↔ FieldWorks Classic sync; several static-diagram and UI iterations still confused people. The user-guide page "How sync works" replaces the static diagram with an interactive, question-driven explainer (`SyncExplainer` component):

- **One fixed picture** — your device → Lexbox holding *two copies* of the project → your colleague's FieldWorks Classic — mirroring the app's own Sync dialog. Every answer replays on the same picture so answers build one mental model.
- **Question chips, not persona tabs** — users know their question ("Why doesn't my colleague see my edit?"), not their category. Selecting one plays a user-paced stepper: a token hops the numbered legs, one sentence per step, a badge naming who acts.
- **Progressive disclosure** — surface level bans internal tech names; an "under the hood" accordion maps friendly names to CRDT/Harmony, Mercurial, FwHeadless for experts.
- **All content in one data module** (`syncScenarios.ts`) — editing prose or fixing a behavior fact is a one-line change; strings are extractable for translation.

Design rationale (evidence: Mayer's segmenting principle, Tversky on animation, NN/g on audience-based navigation and progressive disclosure): learner-paced segments beat continuous animation for novice audiences; autoplay and personas are the classic failure modes.

### Ground-truth facts the explainer encodes (verified against code)

- Device ↔ Lexbox (CRDT) sync is fully automatic: after every edit (~100 ms debounce), on project open, on server push (SignalR), on reconnect; recovery loop every 5 min. `BackgroundSyncService`, `LexboxHubConnection`.
- The Lexbox-internal merge (Lite copy ↔ Classic copy) is **user-triggered only** — the Sync button in FieldWorks Lite or "Sync FieldWorks Lite" on the project page. No scheduler, cron, hg hook, or webhook exists. Jobs queue one project at a time server-wide (`SyncHostedService`); first sync clones + imports and can take minutes.
- Lexbox ↔ FieldWorks Classic is the user's Chorus **Send/Receive**, unchanged.
- Same-field concurrent edits: among Lite users, latest change wins (hybrid logical clock); between Lite and Classic since the last merge, the Classic value wins (`CrdtFwdataProjectSyncService.SyncInternal` applies FwData→CRDT first). Different-field edits both survive. Nothing is surfaced to users as a conflict.
- Found while researching: `backend/FwHeadless/AGENTS.md` wrongly says FwHeadless "detects Mercurial changes" — it only drains the queue fed by `/api/merge/execute`. Fix separately.

### Explainer maintenance rules

Hard caps are load-bearing (they fix the overwhelm that sank the static diagrams): ~8 questions, ≤6 steps each, one sentence per step. New user questions come from support channels; add a scenario in the data module, don't grow the picture. Never introduce doc-only terminology the app doesn't show.

## Site layout on this branch

```text
docs/
├── docusaurus.config.ts # two docs instances, mermaid, no blog
├── user-guide/ # plain-language, translatable
│ └── how-sync-works.mdx # ← SyncExplainer
├── technical/ # architecture, integrations, dev setup
└── src/components/SyncExplainer/
├── index.tsx # rendering (writers never touch)
├── syncScenarios.ts # ALL explainer content
└── styles.module.css
.github/workflows/docs.yaml # build check on PR; Pages deploy on develop
```
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ There are some exceptions:

Summary of setup steps below. See the appropriate file for your operating system for more details:

* [Windows](docs/DEVELOPER-win.md)
* [Linux](docs/DEVELOPER-linux.md)
* [Mac](docs/DEVELOPER-osx.md)
* [Windows](docs/technical/development/setup-windows.md)
* [Linux](docs/technical/development/setup-linux.md)
* [Mac](docs/technical/development/setup-macos.md)

### Prerequisites
* docker and compose
Expand Down Expand Up @@ -194,7 +194,7 @@ Traces can be accessed directly with a URL like this: [https://ui.honeycomb.io/s

In the application, a trace ID (aka "Error code") shown at the bottom of an error message can be Ctrl+clicked to navigate to the trace in Honeycomb.

![Error example](./docs/img/error-example.png)
![Error example](./docs/static/img/error-example.png)

## Testing

Expand Down
20 changes: 20 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Dependencies
/node_modules

# Production
/build

# Generated files
.docusaurus
.cache-loader

# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
107 changes: 107 additions & 0 deletions docs/docusaurus.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {themes as prismThemes} from 'prism-react-renderer';
import type {Config} from '@docusaurus/types';
import type {Options as PresetOptions, ThemeConfig} from '@docusaurus/preset-classic';
import type {Options as DocsOptions} from '@docusaurus/plugin-content-docs';

// Docusaurus appends the path of each doc relative to this directory.
const editUrl = 'https://github.com/sillsdev/languageforge-lexbox/tree/develop/docs';
const repoUrl = 'https://github.com/sillsdev/languageforge-lexbox';

const config: Config = {
title: 'FieldWorks Lite & Lexbox Docs',
tagline: 'Guides for using FieldWorks Lite and Lexbox, and technical documentation for developers.',
favicon: 'img/favicon.png',

future: {
v4: true,
},

// Deploy target and DNS are still a team decision.
// Overridable so CI can deploy previews to another host (e.g. a fork's GitHub Pages).
url: process.env.DOCS_URL ?? 'https://docs.lexbox.org',
baseUrl: process.env.DOCS_BASE_URL ?? '/',

organizationName: 'sillsdev',
projectName: 'languageforge-lexbox',

onBrokenLinks: 'throw',

i18n: {
defaultLocale: 'en',
locales: ['en'],
},

markdown: {
mermaid: true,
},
themes: ['@docusaurus/theme-mermaid'],

presets: [
[
'classic',
{
docs: false,
blog: false,
theme: {
customCss: './src/css/custom.css',
},
} satisfies PresetOptions,
],
],

plugins: [
[
'@docusaurus/plugin-content-docs',
{
id: 'user-guide',
path: 'user-guide',
routeBasePath: 'user-guide',
sidebarPath: './sidebars.ts',
editUrl,
} satisfies DocsOptions,
],
[
'@docusaurus/plugin-content-docs',
{
id: 'technical',
path: 'technical',
routeBasePath: 'technical',
sidebarPath: './sidebars.ts',
editUrl,
} satisfies DocsOptions,
],
],

themeConfig: {
colorMode: {
respectPrefersColorScheme: true,
},
navbar: {
title: 'FieldWorks Lite & Lexbox',
logo: {
alt: 'Lexbox logo',
src: 'img/logo.svg',
srcDark: 'img/logo-dark.svg',
},
items: [
{to: '/user-guide/', label: 'User guide', position: 'left'},
{to: '/technical/', label: 'Technical', position: 'left'},
{href: repoUrl, label: 'GitHub', position: 'right'},
],
},
footer: {
style: 'dark',
links: [
{label: 'Lexbox', href: 'https://lexbox.org'},
{label: 'GitHub', href: repoUrl},
],
copyright: `Copyright © ${new Date().getFullYear()} SIL Global`,
},
prism: {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
},
} satisfies ThemeConfig,
};

export default config;
48 changes: 48 additions & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "lexbox-docs",
"version": "0.0.0",
"private": true,
"packageManager": "pnpm@10.24.0",
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"serve": "docusaurus serve",
"clear": "docusaurus clear",
"write-translations": "docusaurus write-translations",
"typecheck": "tsc"
},
"dependencies": {
"@docusaurus/core": "3.10.2",
"@docusaurus/faster": "3.10.2",
"@docusaurus/plugin-content-docs": "3.10.2",
"@docusaurus/preset-classic": "3.10.2",
"@docusaurus/theme-mermaid": "3.10.2",
"@mdx-js/react": "^3.0.0",
"prism-react-renderer": "^2.3.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.10.2",
"@docusaurus/tsconfig": "3.10.2",
"@docusaurus/types": "3.10.2",
"@types/react": "^19.0.0",
"typescript": "~6.0.2"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 3 chrome version",
"last 3 firefox version",
"last 5 safari version"
]
},
"engines": {
"node": ">=20"
}
}
Loading