texlite 0.7.8 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DESIGN.md ADDED
@@ -0,0 +1,460 @@
1
+ # TexLite design
2
+
3
+ This document records the design goals and the implementation choices that
4
+ shape TexLite. It complements the short [README](README.md) and the
5
+ [operations guide](OPERATIONS.md), which covers installation, configuration,
6
+ and day-to-day operation.
7
+
8
+ ## Design goals
9
+
10
+ - Use the host's TeX Live/LaTeX installation so it can be updated independently.
11
+ - Keep the deployment small: one Node.js process, SQLite, local files, and no
12
+ Redis, MongoDB, reverse proxy, or bundled LaTeX image for the default
13
+ localhost setup.
14
+ - Keep project source, compile output, history, and credentials under one
15
+ configurable data directory so that a complete backup is straightforward.
16
+ - Provide useful real-time collaboration for a small trusted group rather than
17
+ emulate a distributed Overleaf deployment.
18
+ - Prefer durable, explicit boundaries over silently accepting a possibly stale
19
+ or mixed-time source tree.
20
+
21
+ ## Architecture
22
+
23
+ | Area | Implementation |
24
+ | --- | --- |
25
+ | Browser UI | React, Vite, CodeMirror, PDF.js |
26
+ | Localization | Frontend JSON resources plus a server-side error-code catalog, selected from `Accept-Language` |
27
+ | Editor language features | CodeMirror LaTeX syntax/folding, auto-pairs, project completion index, optional Vim mode |
28
+ | Writing assistance | Optional host `harper-cli`, with a linear server-side LaTeX mask and native browser spellcheck fallback; project dictionary stored by the server |
29
+ | API and static server | Fastify, WebSocket |
30
+ | Collaboration | Yjs, y-websocket, awareness messages, y-codemirror.next |
31
+ | Database | SQLite through better-sqlite3, foreign keys and WAL mode |
32
+ | Files | Local project directories under the configured data directory |
33
+ | Compilation | Host `latexmk` and a configured LaTeX engine |
34
+ | Formatting | Browser-side `tex-fmt` WASM package and `bibtex-tidy` |
35
+ | Git backup | Optional host Git and the GitHub REST API |
36
+ | Process management | Foreground `serve`, or the bundled PM2 lifecycle commands |
37
+
38
+ ## Localization
39
+
40
+ The browser owns interface copy through the English and Chinese JSON resources
41
+ in `src/client/locales`. API errors use stable codes rather than route-local
42
+ human text. The server resolves those codes through `src/server/i18n.ts` using
43
+ the request's `Accept-Language` header; the browser API helper always supplies
44
+ its active language, and unmatched or non-browser requests fall back to
45
+ English. This keeps direct API clients usable while allowing the React client
46
+ to retain its more contextual local translations.
47
+
48
+ New user-correctable server failures must use `apiError()` for an immediate
49
+ response or `httpError()` for a thrown response. Do not place translated text
50
+ in route, filesystem, Git, or compiler-control code. Operational logs and
51
+ raw LaTeX output remain in their original form, so they can be searched and
52
+ diagnosed without changing behavior by browser language.
53
+
54
+ ### Citation library
55
+
56
+ The citation library is stored in SQLite and is independent of project source
57
+ files. A `.bib` editor tab parses complete BibTeX entries locally, so saving a
58
+ reference preserves the author's original formatting; importing inserts the
59
+ selected entry at the current editor cursor and requires project write access.
60
+ Each user owns a private library. Citation entries and color tags are scoped to
61
+ the owning user; other users cannot list, search, import, edit, or delete them.
62
+ The homepage keeps Projects as the default view and exposes the
63
+ citation library as a separate management page beside the other management
64
+ controls; the project workspace keeps a smaller `.bib` import dialog for
65
+ in-context writing. Library results are server-filtered and paginated with 60
66
+ entries per page by default (the API accepts a bounded page-size override).
67
+ Library entries are checked in the browser with the `bibtex-tidy` JavaScript
68
+ parser and formatter before a mutation request is sent. Citation keys are unique
69
+ per user without regard to letter case. Creating an existing key is rejected;
70
+ updating an entry or its tags requires the revision last read by the client, so
71
+ concurrent browser sessions report a conflict instead of silently overwriting
72
+ newer content. Entry-text and tag-only mutations use separate endpoints, while
73
+ the explicit save-from-`.bib` action can replace the stored entry without
74
+ discarding its existing tags. The server retains only transport-level size and
75
+ field-shape limits; it does not reparse BibTeX syntax.
76
+
77
+ TexLite is intentionally a single-instance application. The collaboration
78
+ rooms, project mutation queues, compile coordinator, and SQLite database are
79
+ process-local. Startup acquires an atomic `.texlite.lock` directory in the data
80
+ directory and installs its owner record atomically; a live second process is
81
+ rejected, while a stale lock from a dead process can be recovered without
82
+ deleting a lock that is still being initialized. Cluster mode and multiple
83
+ application processes sharing one data directory are not supported.
84
+
85
+ ## Configuration and startup
86
+
87
+ The npm package keeps configuration outside the package installation. The
88
+ effective configuration is selected in this order:
89
+
90
+ 1. `--config PATH`;
91
+ 2. `TEXLITE_CONFIG`;
92
+ 3. `$XDG_CONFIG_HOME/texlite/texlite.config.json`;
93
+ 4. `~/.config/texlite/texlite.config.json`.
94
+
95
+ The data directory defaults to `$XDG_DATA_HOME/texlite` or
96
+ `~/.local/share/texlite`, and can be changed with `storage.dataDir` or
97
+ `TEXLITE_DATA_DIR`. Relative configured paths are resolved relative to the
98
+ configuration file. The effective defaults and accepted ranges are documented
99
+ in the [operations guide](OPERATIONS.md) and are also available through
100
+ `texlite config`.
101
+
102
+ `texlite init` creates the configuration when necessary and creates the first
103
+ administrator. The server refuses to start without at least one active
104
+ administrator; public registration is not enabled. Configuration values are
105
+ validated before environment checks, database opening, or binding the HTTP
106
+ listener. Validation covers paths, limits, engines, timeout/queue settings,
107
+ URLs, and cross-field constraints such as the default engine being present in
108
+ the allowed-engine list.
109
+
110
+ Startup and `doctor` check `latexmk` and every configured LaTeX engine. Git is
111
+ optional: a host without Git can run the editor and compiler, while Git is
112
+ checked on demand when an owner opens or uses Git integration (or explicitly
113
+ with `texlite doctor --git`). Formatting is also optional: the browser loads
114
+ the bundled npm `tex-fmt` WASM module and `bibtex-tidy` only when formatting is
115
+ requested. TexLite never installs or updates TeX packages.
116
+
117
+ `texlite serve` runs in the foreground and is suitable for debugging, Docker,
118
+ or systemd. `start`, `status`, `stop`, `restart`, and `logs` use the bundled
119
+ PM2 dependency. Managed startup waits for both PM2 and the HTTP health probe;
120
+ status has a colored systemctl-style view and a `--json` form for scripts.
121
+ PM2 7.0.3 currently declares `js-yaml@4.3.0`, so dependency audits may report
122
+ the upstream GHSA-5p4m-2wfm-xmqj advisory. TexLite invokes PM2 through its
123
+ JavaScript API and does not load user- or project-supplied YAML configuration;
124
+ deployments requiring a zero-advisory dependency tree can instead supervise
125
+ `texlite serve` with systemd or Docker until PM2 updates that dependency.
126
+ Expired login sessions are pruned at startup and periodically while the
127
+ process is running, rather than merely being ignored during authentication.
128
+ Administrators can open the System status view (or authenticated
129
+ `GET /api/health/metrics`) for in-memory uptime, resource, queue, collaboration,
130
+ event-loop, and recent latency summaries. These metrics intentionally exclude
131
+ source text, passwords, tokens, and comment content.
132
+
133
+ ## Collaboration and source persistence
134
+
135
+ Each open project has one Yjs room. Awareness data provides active-session
136
+ avatars, user names, permissions, file paths, cursor colors, and the ten-session
137
+ project limit. Ordinary Yjs edits remain concurrent; source-tree replacement
138
+ operations temporarily enter maintenance, notify/close collaborators, and
139
+ rotate the collaboration epoch so an old offline draft cannot overwrite a
140
+ checkout or history restore. Browser IndexedDB retains unsent updates across a
141
+ transient disconnect.
142
+
143
+ Read permission is intentionally different from edit permission: read-only
144
+ members cannot modify source files, but can view the project and add or reply
145
+ to source comments. Comments are anchored to source offsets and selected text,
146
+ can be resolved, edited, deleted, and replied to. If an author is removed,
147
+ the record remains visible as “Deleted User”.
148
+
149
+ The collaboration service uses a versioned handshake and a versioned epoch
150
+ marker. When a browser from an incompatible release connects, it is forced to
151
+ reload before it can decode or send source updates. Protocol-only migrations
152
+ preserve the browser's offline draft; source-tree replacements still clear the
153
+ draft because the server tree is authoritative.
154
+
155
+ The collaboration service persists dirty text with atomic temporary-file writes
156
+ and returns a receipt containing `revision`, `persistedAt`, `ok`, and failed
157
+ paths. Failed writes remain dirty and are retried in the background. Every
158
+ ordinary source operation must accept a successful receipt: the coordinator
159
+ returns `409 SOURCE_FLUSH_FAILED` with `failedPaths` instead of proceeding with
160
+ a stale disk tree. This applies to Git checkout/restore, history restore,
161
+ file/folder writes, moves/deletes, project deletion, and consistent reads. A
162
+ flush requested while a snapshot barrier is active is deferred until the
163
+ barrier closes, so the browser does not receive a false failure for an edit that
164
+ was intentionally held in memory. If a collaborative text edit exceeds the
165
+ configured limit, it is restored to the last durable content and the flush
166
+ receipt identifies the rejected path.
167
+
168
+ Yjs state persistence is reserved for source and HTTP-originated text updates.
169
+ Ephemeral metadata such as compile status, file-list revisions, and comment or
170
+ dictionary invalidation markers is broadcast live and reconstructed from the
171
+ database/source tree after restart; metadata-only events therefore do not cause
172
+ another full synchronous Yjs state rewrite. During room recovery the server
173
+ removes old markers and validates any queued/running compile state against
174
+ `compile_runs`. The collaboration handshake also sends a small
175
+ server-authoritative compile-state snapshot, so a stale browser IndexedDB entry
176
+ cannot make a read-only workspace appear permanently busy.
177
+
178
+ Each collaboration message refreshes the account record before applying an
179
+ update. Disabling a user, deleting the account, or changing project membership
180
+ therefore takes effect for an already-open socket rather than relying only on
181
+ the next reconnect. Binary uploads also publish a source-tree event so other
182
+ open workspaces refresh their file lists even though binary files are not Yjs
183
+ text objects.
184
+
185
+ `ProjectMutationCoordinator` has two related controls:
186
+
187
+ | Operation class | Examples | Coordination behavior |
188
+ | --- | --- | --- |
189
+ | Serialized source operation | File writes, settings, Git metadata, project-wide replace | Waits for queued operations, waits for a ready room, flushes the room, then runs while holding the per-project queue. |
190
+ | Exclusive source replacement | Git checkout/restore, history restore, project deletion, path move | Waits for active compilation, flushes successfully, enters maintenance, performs the replacement, and resets the collaboration epoch. |
191
+ | Consistent source read | Archive/download, raw source, history comparison, search, outline, completion index, Git diff/history | Uses the queue and a short snapshot barrier. The barrier blocks disk autosaves while an asynchronous scan/copy runs, then validates the deferred flush before returning. |
192
+ | Background compilation | `latexmk` on an immutable snapshot | Holds a compile reservation but not the ordinary source queue, so editing, source reads, and the retained PDF can continue. |
193
+ | Compile-state cleanup | Cache/artifact recovery | Waits for active compiles and serializes output removal without disconnecting collaborators. |
194
+ | Published PDF read | PDF.js/range requests | Reads an immutable published bundle directly and does not wait for cold Yjs-room initialization; a concurrent cleanup may produce a normal 404. |
195
+ | Cold file-list read | Project file tree | Uses the queue and a short barrier, but does not wait for a cold Yjs-room hydration; a concurrent cleanup is treated as a normal missing entry. |
196
+
197
+ The source tree is checked with `lstat`-based path walks. ZIP imports, project
198
+ duplication, Git checkout, file listing, and source resolution reject symbolic
199
+ links (except that deletion may address a final link itself without following
200
+ its target). This prevents a project path from escaping its source directory.
201
+
202
+ ## Editor, files, and navigation
203
+
204
+ The editor is CodeMirror-based and provides LaTeX syntax highlighting, folding,
205
+ auto-pairs (including `\begin{...}`/`\end{...}`), indentation, Vim mode when
206
+ explicitly enabled, and completion items from built-in LaTeX plus project
207
+ `.tex`, `.sty`, and `.cls` definitions and BibTeX labels. Completion indexes and
208
+ outlines are metadata-keyed and coalesced; a source-tree change invalidates the
209
+ corresponding cache.
210
+
211
+ Opening `.tex`, `.bib`, `.sty`, or `.cls` files in editor tabs is an optional
212
+ per-user/per-project preference and is off by default. The active tab is
213
+ highlighted and keyboard accessible. PDF/SyncTeX synchronization is available
214
+ only for the current `.tex` root document; a non-root tab remains editable but
215
+ does not claim a PDF location.
216
+
217
+ Each project collaboration object owns one Yjs undo manager per source file.
218
+ Editor tab remounts therefore preserve Ctrl/Cmd+Z and Vim undo history without
219
+ leaving obsolete CodeMirror/Yjs observers behind. Managers are released when
220
+ the collaboration object is destroyed or loses edit permission.
221
+
222
+ TexLite probes the optional host `harper-cli` command at startup. It runs each
223
+ check in a private temporary TeX file after a narrow, linear server-side LaTeX
224
+ mask has removed comments, commands, references, math, tables, option syntax,
225
+ and code-like environments. This avoids browser/WASM startup work and keeps
226
+ malformed delimiters bounded. The service serializes checks, coalesces identical
227
+ source inputs for a short window, and returns Harper's safe replacement
228
+ suggestions. Spelling uses a red wavy underline and grammar uses yellow; a
229
+ context menu offers suggestions, while read-only members can inspect but cannot
230
+ apply them. The shared project dictionary is kept in SQLite and filters
231
+ project-specific spelling results in the browser. If the command is absent or
232
+ fails, CodeMirror enables the browser's built-in English spellchecker until it
233
+ retries.
234
+
235
+ Formatting is independent from the editor's local appearance. A user can
236
+ manually format a selection or enable the per-user/per-project “format before
237
+ compile” preference. The browser uses bundled `tex-fmt` WASM for `.tex`, `.cls`,
238
+ and `.sty`, and `bibtex-tidy` for `.bib`; the editor settings also provide a
239
+ per-user/per-project TOML options string passed to `tex-fmt`. The formatter and
240
+ text-diff calculation run in a lazily loaded Web Worker, so opening a project
241
+ does not wait for them and formatting does not block the editor UI. Formatter
242
+ diagnostic logs are shown as expandable warnings. A formatter failure reports
243
+ an error but does not prevent compilation. There is no silent Prettier fallback.
244
+
245
+ Before a formatter computes a replacement it acquires a short-lived, per-file
246
+ lease from the live Yjs room. The lease is held only for the format/snapshot,
247
+ final Yjs apply, and a durability flush; ordinary typing and compilation are
248
+ not blocked. A second formatter for the same path waits in a bounded FIFO queue,
249
+ and the server grants it only after the first session's update has been
250
+ received and flushed. Leases carry an expiring random token, renew before the
251
+ final apply, and are released automatically on timeout, permission loss, or
252
+ WebSocket disconnect. Because the lease is process-local, it is deliberately
253
+ scoped to TexLite's single-instance deployment; it is not a distributed lock.
254
+ If a source edit arrives while a formatter is working, the client discards the
255
+ stale replacement rather than applying offsets to newer text.
256
+
257
+ Project duplication flushes the live source room and copies the tree under a
258
+ short read barrier. Uploading a replacement text file also re-anchors existing
259
+ source comments against the old and new contents before notifying collaborators.
260
+
261
+ The outline follows `\input`, `\include`, and `\subfile` references and
262
+ jumps to source lines. The source and PDF panes expose explicit SyncTeX arrows,
263
+ and PDF double-click can request the corresponding source location. Search and
264
+ replace is project-wide, staged as one serialized operation, and records one
265
+ history version. Structured compile diagnostics resolve project-relative
266
+ file names and line numbers; the raw `latexmk` transcript remains available.
267
+
268
+ ## Compilation and retained output
269
+
270
+ The project setting supplies the default root document. Project settings list
271
+ and accept only `.tex` files containing a real `\documentclass` declaration;
272
+ an imported project with exactly one `.tex` file may use that file as a
273
+ compatibility fallback. The server enforces the same rule for compile and
274
+ preview routes. A browser session may select another detected root, and only
275
+ the currently selected root is compiled. Compile state, logs, retained PDF,
276
+ artifacts, outline, and SyncTeX are keyed by root, so collaborators working on
277
+ different roots do not replace each other's result or compile notification.
278
+
279
+ Compilation follows this sequence:
280
+
281
+ 1. Admission validates permissions/root selection and coalesces requests by
282
+ project, root, and a cheap source/settings generation.
283
+ 2. After coalescing, TexLite flushes the room and captures a source snapshot.
284
+ A short snapshot barrier prevents autosave from modifying the source tree
285
+ while the asynchronous copy and digest run. Edits that arrive during the
286
+ barrier remain in Yjs memory until the snapshot is complete, so the
287
+ captured tree is internally consistent even when it is already older than
288
+ the live editor. Such a compile is accepted and the workspace labels the
289
+ retained PDF as based on an earlier snapshot; only a failed post-barrier
290
+ flush remains retryable.
291
+ 3. Changed files are synchronized into an incremental compile workspace keyed
292
+ by project, root, engine, latexmkrc, and compiler arguments. The cache is
293
+ reused only for the same root; root-specific caches can compile concurrently
294
+ within the global `maxCompileJobs` limit.
295
+ 4. `latexmk` runs with `-norc` and `-synctex=1`, line-oriented error output, and shell escape
296
+ disabled by default. Its process group, including `pdflatex`, BibTeX/Biber,
297
+ and other descendants, is terminated on timeout. latexmk itself performs
298
+ the repeated passes required by bibliography documents.
299
+ 5. A successful PDF, SyncTeX file, log, and generated artifacts are copied into
300
+ an immutable run bundle. A small atomic manifest switch publishes it as the
301
+ latest result; an older bundle is retained briefly so an already-open PDF
302
+ request can finish.
303
+
304
+ The previous successful PDF remains visible during a new compile. The latest
305
+ published bundle is recovered after a restart and can be served before a cold
306
+ collaboration room is initialized. PDF requests support range responses for
307
+ PDF.js and expose the successful compile time and artifact size. The default
308
+ automatic loading policy uses a complete, cache-friendly response through 5 MB
309
+ and enables PDF.js byte-range loading above that threshold; deployments can
310
+ force either mode without changing project compiler settings. The output panel groups the PDF,
311
+ log, warnings, errors, generated artifacts, and recovery actions; clean-cache
312
+ and clean-artifact actions are for recovery rather than routine compilation.
313
+ Compile responses expose `Server-Timing` measurements for snapshot, cache
314
+ synchronization, LaTeX execution, artifact publication, and total request time
315
+ so queue and rendering regressions can be diagnosed. Artifact listing and
316
+ download endpoints treat removal of a run between manifest lookup and file
317
+ read as an empty list or 404, rather than exposing a filesystem race as a 500.
318
+ TexLite does not keep an unlimited browsable compile history: old unsuccessful
319
+ run rows and unreferenced bundles are pruned, while the latest successful
320
+ result for each root is retained.
321
+
322
+ ## History and recovery
323
+
324
+ History records initial state, acknowledged collaborative saves, file/source
325
+ operations, compiler settings, Git operations, checkpoints, and restores.
326
+ Autosaves by the same author are coalesced within a two-minute window. File
327
+ contents are complete SHA-256-addressed objects; unchanged files are reused
328
+ across manifests. The retention defaults are 200 ordinary unlabeled versions
329
+ and 128 MB of deduplicated objects per project. Initial and labeled versions,
330
+ plus the current internal baseline, are protected and can make the soft limit
331
+ temporarily exceed its target. Retention pruning batches reference accounting
332
+ and removes unreferenced objects.
333
+
334
+ Owners can view storage statistics, delete one version, or clear all history
335
+ without changing current source files. Restore is an exclusive source
336
+ operation, reanchors comments against the before/after text, updates project
337
+ settings when restoring a complete version, and resets the collaboration epoch.
338
+ History is a recovery mechanism, not a substitute for backing up the complete
339
+ data directory.
340
+
341
+ ## GitHub backup
342
+
343
+ The Git panel is project-owner-only. Git is optional at startup and is checked
344
+ when Git integration is used. A per-project GitHub token is encrypted in
345
+ SQLite; it is never placed in a remote URL or command-line argument. The local
346
+ repository lives in the project source directory, with temporary identity:
347
+
348
+ ~~~text
349
+ user.name = project owner's username
350
+ user.email = <username>@texlite.com
351
+ ~~~
352
+
353
+ For a fine-grained GitHub token in a trusted deployment, grant repository
354
+ Administration and Contents read/write permissions. “All repositories” is the
355
+ practical choice when a repository may be created after token configuration.
356
+ Only the owner can commit, push, checkout, restore, or configure the project
357
+ repository. Commit messages are entered explicitly. Normal checkout preserves
358
+ local changes and refuses conflicts; only the explicit force option discards
359
+ tracked, untracked, and ignored working-tree files. The Git operations use the
360
+ same project coordination and durable-flush boundary as other source
361
+ replacements.
362
+
363
+ ## Data, backup, tags, and deletion
364
+
365
+ The default data layout is:
366
+
367
+ ~~~text
368
+ <data-dir>/
369
+ ├── .texlite.lock
370
+ │ └── owner.json
371
+ ├── texlite.db
372
+ ├── texlite.db-wal
373
+ ├── texlite.db-shm
374
+ ├── git-token.key
375
+ ├── tmp/ and trash/
376
+ └── projects/
377
+ └── <project-id>/
378
+ ├── source/
379
+ └── output/
380
+ └── .texlite/
381
+ ├── cache/
382
+ ├── runs/
383
+ └── history/
384
+ ~~~
385
+
386
+ Back up `texlite.db`, `git-token.key`, and `projects/` together. Include the
387
+ SQLite WAL files in a live filesystem backup or use a SQLite-aware backup
388
+ procedure. The token encryption key is required to recover saved GitHub
389
+ credentials.
390
+
391
+ Tags and archive state are private to each user. A project can therefore have
392
+ different labels, filters, and archived/active visibility for different
393
+ collaborators. Deleting a project removes its database rows and source/output
394
+ directory; deletion uses a temporary trash rename when possible and a startup
395
+ reaper cleans abandoned trash/temp entries. Deleting a user removes sessions,
396
+ memberships, private tags, and comments remain attributable as “Deleted User”.
397
+ An administrator can transfer the user's owned projects to the current
398
+ administrator or delete them with their files. Project transfer keeps the old
399
+ owner as an editor and clears the project GitHub token so the new owner must
400
+ configure their own credential. Administrators do not otherwise receive
401
+ implicit access to another user's projects; they see a project only when they
402
+ own it or it has been explicitly shared with them. The last active
403
+ administrator cannot be removed or disabled.
404
+
405
+ ## Known limitations and TODO
406
+
407
+ These items describe remaining engineering work rather than promises of a
408
+ particular release. Security items are especially important if the deployment
409
+ model expands beyond a small group of trusted users on localhost.
410
+
411
+ ### Required before untrusted or public deployment
412
+
413
+ - [ ] Isolate compilation. LaTeX is not a security sandbox, and a project
414
+ `latexmkrc` is executable Perl. Prefer a dedicated low-privilege account or
415
+ container/sandbox and apply CPU, memory, process, and filesystem limits.
416
+ - [ ] Add deployment-aware HTTP protections: configurable trusted-proxy
417
+ handling, explicit Origin/CSRF validation for deployments that are not
418
+ localhost-only, and conservative response/security headers. Login limiting
419
+ and strict session-cookie defaults are present but are not a complete public
420
+ deployment policy.
421
+ - [x] Validate every PDF annotation URL against an explicit protocol allowlist,
422
+ including the URL that PDF.js labels as sanitized, before creating an
423
+ external browser link.
424
+ - [x] Harden compile-output serving. PDF and artifact listing/stat/copy paths
425
+ treat cleanup races as an empty result or 404 and never expose an expected
426
+ missing-file race as a 500.
427
+
428
+ ### Correctness and recovery
429
+
430
+ - [ ] Make database/filesystem lifecycle operations fully crash-recoverable.
431
+ Project creation/import/duplication, project deletion, user cleanup, history
432
+ deletion, and temporary downloads still need explicit tombstones or startup
433
+ reconciliation for every failure point.
434
+ - [x] Refresh collaboration account/access data at message time and disconnect
435
+ revoked users; membership changes are also pushed to open clients.
436
+ - [x] Strengthen source-comment re-anchoring. A diff-mapped range is accepted
437
+ only when it still contains the original selected text; replacements and
438
+ ambiguous repeated text require matching surrounding context or are marked
439
+ orphaned for manual review.
440
+ - [x] Detect root documents as source files are opened and edited, ignoring
441
+ comments and common verbatim environments. The settings API and UI expose
442
+ only files with a real `\documentclass` declaration as candidates; a sole
443
+ `.tex` file remains a compatibility fallback for single-file imports. The
444
+ editor applies the same rule instead of trusting the configured path.
445
+ - [ ] Add failure-injection tests for crashes between source snapshot,
446
+ publication, database compile status updates, project deletion, and history
447
+ cleanup.
448
+
449
+ ### Performance and maintainability
450
+
451
+ - [ ] Move large history snapshots, retention scans, and object garbage
452
+ collection away from synchronous request/startup paths, and add integrity
453
+ recovery for missing or orphaned history objects.
454
+ - [ ] Record per-project queue wait time and add a real-browser concurrency
455
+ benchmark covering a long compile alongside editing, PDF range requests,
456
+ SyncTeX, cleanup, and Git checkout.
457
+ - [ ] Continue modularization of the largest files, especially
458
+ `server/app.ts`, `server/collaboration.ts`, and `client/App.tsx`, so
459
+ authorization and coordination rules are easier to audit and test
460
+ independently.
package/NPM_TESTING.md ADDED
@@ -0,0 +1,130 @@
1
+ # Testing the npm package locally
2
+
3
+ Use the packed npm artifact for release testing. Installing the tarball into a
4
+ temporary npm prefix is closer to a real global installation than running the
5
+ source tree directly, and it does not modify the host's global npm packages.
6
+
7
+ ## Build and inspect the package
8
+
9
+ Run the project checks first:
10
+
11
+ ~~~bash
12
+ npm run typecheck
13
+ npm test
14
+ npm run build
15
+ npm pack --dry-run
16
+ ~~~
17
+
18
+ The dry run lists the files that would be published. Check that it contains the
19
+ compiled server and `dist/client`, and does not contain local databases,
20
+ projects, credentials, or development-only files.
21
+
22
+ ## Install the tarball into an isolated prefix
23
+
24
+ Create a temporary directory, pack the current version, and install it without
25
+ touching the real global npm installation:
26
+
27
+ ~~~bash
28
+ TEST_ROOT="$(mktemp -d /tmp/texlite-package-test.XXXXXX)"
29
+ PACKAGE_VERSION="$(node -p "require('./package.json').version")"
30
+ PACKAGE_FILE="texlite-${PACKAGE_VERSION}.tgz"
31
+
32
+ npm pack --pack-destination "$TEST_ROOT"
33
+ npm_config_cache="$TEST_ROOT/npm-cache" \
34
+ npm install --prefix "$TEST_ROOT/prefix" --global "$TEST_ROOT/$PACKAGE_FILE"
35
+
36
+ "$TEST_ROOT/prefix/bin/texlite" --version
37
+ "$TEST_ROOT/prefix/bin/texlite" --help
38
+ ~~~
39
+
40
+ The version in `PACKAGE_FILE` is read from `package.json`, so the commands also
41
+ work after a version bump.
42
+
43
+ ## Test configuration, data paths, and environment checks
44
+
45
+ Use temporary XDG directories and a non-interactive administrator account:
46
+
47
+ ~~~bash
48
+ XDG_CONFIG_HOME="$TEST_ROOT/config" \
49
+ XDG_DATA_HOME="$TEST_ROOT/data" \
50
+ TEXLITE_SITE_NAME='TexLite Package Test' \
51
+ TEXLITE_ADMIN_EMAIL='' \
52
+ TEXLITE_INIT_USERNAME=admin \
53
+ TEXLITE_INIT_DISPLAY_NAME=Administrator \
54
+ TEXLITE_INIT_PASSWORD='use-a-password-of-at-least-8-characters' \
55
+ "$TEST_ROOT/prefix/bin/texlite" init
56
+
57
+ XDG_CONFIG_HOME="$TEST_ROOT/config" \
58
+ XDG_DATA_HOME="$TEST_ROOT/data" \
59
+ "$TEST_ROOT/prefix/bin/texlite" config
60
+
61
+ XDG_CONFIG_HOME="$TEST_ROOT/config" \
62
+ XDG_DATA_HOME="$TEST_ROOT/data" \
63
+ "$TEST_ROOT/prefix/bin/texlite" doctor
64
+ ~~~
65
+
66
+ The `config` output should show the temporary configuration and data paths.
67
+ `doctor` verifies the configuration, database, administrator, and host LaTeX
68
+ commands. Git is optional; add `--git` when Git integration should be checked.
69
+
70
+ To verify the configurable data directory explicitly:
71
+
72
+ ~~~bash
73
+ XDG_CONFIG_HOME="$TEST_ROOT/config" \
74
+ XDG_DATA_HOME="$TEST_ROOT/data" \
75
+ TEXLITE_DATA_DIR="$TEST_ROOT/custom-data" \
76
+ "$TEST_ROOT/prefix/bin/texlite" config
77
+ ~~~
78
+
79
+ ## Test the foreground server
80
+
81
+ Run the installed package in the foreground:
82
+
83
+ ~~~bash
84
+ XDG_CONFIG_HOME="$TEST_ROOT/config" \
85
+ XDG_DATA_HOME="$TEST_ROOT/data" \
86
+ "$TEST_ROOT/prefix/bin/texlite" serve
87
+ ~~~
88
+
89
+ Open <http://127.0.0.1:3000> in a browser and exercise login, project
90
+ creation, editing, compilation, file upload, comments, and PDF preview. Press
91
+ Ctrl+C to stop the server.
92
+
93
+ The package requires Node.js 24 or newer and the LaTeX engines enabled in the
94
+ selected configuration.
95
+
96
+ ## Test the PM2 lifecycle in isolation
97
+
98
+ Use both a private `PM2_HOME` and a test-only port. Every lifecycle command must
99
+ receive the same environment so it addresses the same configuration and PM2
100
+ daemon without touching the host user's real processes:
101
+
102
+ ~~~bash
103
+ PM2_HOME="$TEST_ROOT/pm2" \
104
+ XDG_CONFIG_HOME="$TEST_ROOT/config" \
105
+ XDG_DATA_HOME="$TEST_ROOT/data" \
106
+ TEXLITE_PORT=3300 \
107
+ "$TEST_ROOT/prefix/bin/texlite" start
108
+
109
+ PM2_HOME="$TEST_ROOT/pm2" \
110
+ XDG_CONFIG_HOME="$TEST_ROOT/config" \
111
+ XDG_DATA_HOME="$TEST_ROOT/data" \
112
+ TEXLITE_PORT=3300 \
113
+ "$TEST_ROOT/prefix/bin/texlite" status
114
+
115
+ PM2_HOME="$TEST_ROOT/pm2" \
116
+ XDG_CONFIG_HOME="$TEST_ROOT/config" \
117
+ XDG_DATA_HOME="$TEST_ROOT/data" \
118
+ TEXLITE_PORT=3300 \
119
+ "$TEST_ROOT/prefix/bin/texlite" stop
120
+ ~~~
121
+
122
+ The custom XDG configuration receives a path-derived PM2 name such as
123
+ `texlite-a1b2c3d4`; the private `PM2_HOME` additionally prevents the test daemon
124
+ and logs from mixing with the normal installation.
125
+
126
+ Remove the temporary test installation when finished:
127
+
128
+ ~~~bash
129
+ rm -rf "$TEST_ROOT"
130
+ ~~~