texlite 0.7.8 → 0.8.1

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