Segundo cerebro científico con LaTeX en vivo, voz y bóveda segura.
Project description
lablog
A live LaTeX laboratory notebook for working scientists.
Part of the Pharos Project · José Labarca Baeza · USM, Valparaíso
| About | Features | Architecture | Installation | Quick start | Configuration |
| Editor | Security | Testing | Publishing | Roadmap | License |
About
lablog is a research-grade notebook that lives where the experiment happens. It pairs a structural LaTeX editor with live preview, executable cells, voice dictation and an immutable, event-sourced history so that the record of an investigation can be reconstructed exactly as it unfolded.
Unlike a typesetting tool used after the experiment, lablog is meant to run while the
work is in progress — gloves on, instrument in front, hands occupied. It is built on
the premise that the act of writing the paper should not be separated from the act of
producing the data.
| Tool | When you use it |
|---|---|
| Overleaf | After the experiment, when the manuscript is being prepared. |
| TeXstudio / TeXmacs | For traditional LaTeX editing on a desktop. |
| lablog | During the experiment: to dictate, execute and preserve as it happens. |
Features
| Module | Capability |
|---|---|
| Structural LaTeX renderer | Sections, emphasis, lists, hyperlinks, and inline mathematics flow within paragraphs. KaTeX powers display environments (align, equation, gather, cases, pmatrix) with automatic equation numbering. |
| Editor | Line gutter, parameter overlay, auto-save, Ctrl+F find & replace with hit counter, Ctrl+Z / Ctrl+Y undo / redo, Ctrl+B/I/E bold / italic / math, smart delimiter wrapping, snippet and symbol insertion at cursor. |
| Voice dictation | Browser SpeechRecognition with an intent detector and local Whisper pipeline. A session safety timeout prevents the recogniser from hanging if the API stalls. |
| Executable cells | Write \begin{python}...\end{python} and run real Jupyter kernels in place. Stdout, results and figures are captured back into the document; the kernel is interrupted on timeout to prevent runaway code. |
| Per-page vault | Attach images, CSV, PDF, DOCX, audio and scripts; preview them without leaving the notebook. Metadata is written atomically; uploads are sanitised against path traversal and bounded by an explicit size limit. |
| Event-sourced history | Every edit, execution and attachment is appended as an immutable JSONL event. The current state is a deterministic projection — reproducible, auditable, and the foundation for future time-travel UI. |
| Project grouping | Pages cluster into projects directly from the sidebar; renaming, deletion and reorganisation are first-class operations. |
| Export | LaTeX (.tex), plain text, HTML, PDF and DOCX via pandoc; Jupyter notebook (.ipynb); static site for GitHub Pages; Canva-ready HTML. Titles are escaped to prevent LaTeX injection. |
| Diagram workbench | Twelve parameterized presets (circuits, control, mechanics, optics, Feynman). Dial values, re-apply TikZ, optional PySpice cells with numpy fallback, dual highlight (editor line + colored circuitikz nodes). |
| Personalization | Density, editor font, Nord palette, reduced motion, lab/paper/teaching profiles, exportable preferences JSON, configurable keyboard shortcuts. |
Architecture
%%{init: { 'theme': 'base', 'themeVariables': { 'primaryColor':'#1C1C1E', 'primaryTextColor':'#F2F2F7', 'primaryBorderColor':'#48484A', 'lineColor':'#A1A1A6', 'secondaryColor':'#48484A', 'tertiaryColor':'#1C1C1E' }}}%%
flowchart LR
subgraph UI[lablog UI]
direction TB
React["React 19 + Vite"]
Tailwind["Tailwind CSS v4"]
Zustand["Zustand store"]
KaTeX["KaTeX renderer"]
end
subgraph Engine[lablog Engine]
direction TB
FastAPI["FastAPI"]
Events["Event Store (JSONL)"]
Projector["Projector"]
AST["LaTeX AST"]
Jupyter["Jupyter / Python"]
Vault["Vault"]
end
User((Researcher))
User --> UI
UI <-->|HTTP| Engine
Engine -->|export| Site["Static site"]
Site -->|GitHub Actions| Pages["GitHub Pages"]
The engine is decoupled from the interface: it can be driven from the CLI, embedded in
tests, or replaced by another front-end. The projector reconstructs the current
DocumentNode from the event log; the renderer reduces that tree to HTML.
Module map
| Path | Responsibility |
|---|---|
src/lablog/api.py |
FastAPI routes; export pipeline; vault uploads. |
src/lablog/event_store.py |
Append-only JSONL log; path-validated page identifiers. |
src/lablog/events.py |
Event schema and constructors. |
src/lablog/projector.py |
Pure folding of events into a DocumentNode. |
src/lablog/latex_ast.py |
Tokeniser; environment whitelist; round-trip serialiser. |
src/lablog/code_engine.py |
Jupyter kernel manager with enforced timeout. |
src/lablog/vault.py |
Attachment store with atomic metadata and time-locked deletion. |
src/lablog/exporter.py |
Static site exporter for GitHub Pages. |
ui/src/lib/latex-render.ts |
Structural renderer (prose × mathematics). |
ui/src/components/editor/latex-editor.tsx |
Editor, find & replace, undo / redo, shortcuts. |
ui/src/components/preview/latex-preview.tsx |
Node coalescing & live preview. |
ui/src/stores/app-store.ts |
Single source of truth on the client; persistent state. |
Stack
| Layer | Stack |
|---|---|
| Engine | Python 3.11+, FastAPI, Pydantic, Jupyter Client, faster-whisper. |
| Persistence | JSONL event log; atomic metadata writes; deterministic projection. |
| Interface | React 19, TypeScript, Vite 8, Tailwind CSS v4, Zustand, shadcn/ui, Radix. |
| Mathematics | KaTeX with automatic numbering for display environments. |
| Tooling | uv (Python), npm (Node), Ruff, Mypy (strict), oxlint, pytest, GitHub Actions. |
Installation
From PyPI (recommended)
pip install jose-labarca-lablog
lablog serve
The wheel bundles the compiled UI, so a simple pip install is enough to run the
application. The optional desktop extra adds the native window:
pip install jose-labarca-lablog[desktop]
lablog app
From source
Prerequisites. Python 3.11 or newer, Node 22,
uv, andnpm. Optional:pandocandxelatex(orpdflatex) for PDF and DOCX export.
git clone https://github.com/kegouro/lablog.git
cd lablog
uv sync --extra dev
source .venv/bin/activate
cp .env.example .env
cd ui && npm install && cd ..
Optional extras (the core install stays lean):
| Extra | Command | Adds |
|---|---|---|
| Desktop app | uv sync --extra desktop |
pywebview — native window via lablog app. |
| Offline voice | uv sync --extra voice |
local Whisper model + audio capture (several hundred MB). |
Quick start
Run the engine
source .venv/bin/activate
uvicorn lablog.api:app --host 127.0.0.1 --port 8000 --reload
Run the interface
cd ui
npm run dev
| Endpoint | URL |
|---|---|
| User interface | http://127.0.0.1:5173 |
| API | http://127.0.0.1:8000/api/v1 |
| Liveness probe | http://127.0.0.1:8000/api/v1/health |
For a production build served by the engine itself:
cd ui && npm run build && cd ..
uvicorn lablog.api:app --host 127.0.0.1 --port 8000
The interface is then served statically from ui/dist by FastAPI.
Desktop application
lablog runs as a native desktop window — no browser, no internet. The engine
binds to 127.0.0.1 on an ephemeral port inside the process and the interface opens in
the operating system's own webview (WKWebView on macOS, WebView2 on Windows, WebKitGTK on
Linux). Every asset — JavaScript, CSS, fonts and the KaTeX renderer — is
bundled, so the application is fully offline.
uv sync --extra desktop # installs pywebview
cd ui && npm run build && cd ..
lablog app # opens the native window
Offline note. The core notebook (editor, live preview, executable cells, vault) is entirely offline. The in-browser dictation button relies on the platform speech API and degrades gracefully when offline; for offline dictation install the local Whisper model with
uv sync --extra voice.
Packaging a portable bundle
To ship a self-contained folder that runs without any Python install:
./scripts/package_desktop.sh # builds the UI, then PyInstaller
# → dist/lablog/ (zip it and run dist/lablog/lablog)
The bundle is described by lablog.spec. It includes ui/dist and the
Jupyter kernel used for cell execution, and excludes the heavy voice model by default.
The kernel and pyzmq are discovered dynamically, so on a fresh platform the spec's
collect_all lists may need a small adjustment — treat it as a verified starting
point rather than a one-click cross-platform installer.
Real PDF compilation
The live preview is fast but approximate — it renders the common subset of LaTeX with KaTeX and a focused HTML renderer. For a faithful document, lablog compiles the page to a real PDF with Tectonic (a self-contained XeTeX engine). The first compile downloads and caches the required TeX packages once; every subsequent compile is fully offline.
- The Compilar PDF button lives in the preview header; the preview itself is labelled "Aproximada" so the distinction is explicit.
- Executable
\begin{python}cells are rendered into the PDF as code + output + figure (code and output viafancyvrb, figures via\includegraphics). - Compilation runs asynchronously with a hard timeout; runaway documents return
504instead of hanging the engine. Output is cached by document hash, so recompiling an unchanged page is instant. - When compilation fails, the error panel maps TeX errors back to the source via
injected
% lablog-srcmarkers, showing "Celda N · línea M: message" rather than a meaningless line in the generated.tex.
Security. Tectonic runs without
--shell-escape— LaTeX cannot execute operating-system commands. lablog is local and single-user; the compile endpoint assumes you are compiling your own content and must not be exposed publicly without adding rate limiting and isolation.
Engine note. If
tectonicis already on yourPATHit is used directly. Otherwise the preview shows an install banner that downloads the pinned, checksum-verified binary once toLABLOG_DATA_DIR/bin/and warms the common packages (offline afterwards). When a managed binary falls behind the version this app pins, the banner offers a verified re-install — it never fetches "latest" at runtime, preserving the checksum trust chain.
Configuration
All configurable paths and addresses are surfaced through environment variables.
See .env.example for the full list.
| Variable | Default | Purpose |
|---|---|---|
LABLOG_DATA_DIR |
~/.lablog |
Root for events, vault, figures and exported site. |
LABLOG_HOST |
127.0.0.1 |
Bind address for the API. |
LABLOG_PORT |
8000 |
Port for the API. |
LABLOG_CORS_ORIGINS |
http://localhost:5173, http://127.0.0.1:5173 |
Comma-separated CORS origins. |
LABLOG_SITE_DIR |
${data_dir}/site |
Destination of the static site exporter. |
Note.
lablognever reads secrets from the data directory and never embeds them in exports. TreatLABLOG_DATA_DIRas personal notes; commit only what you intend to publish.
Editor experience
| Action | Shortcut |
|---|---|
| Find & replace (in-editor) | Ctrl+F · Ctrl+H |
| Next / previous match | Enter · Shift+Enter |
| Undo / redo | Ctrl+Z · Ctrl+Y |
| Bold · Italic · Inline math | Ctrl+B · Ctrl+I · Ctrl+E |
| Indent selection | Tab |
| Command palette | Ctrl+K |
| Wrap selection in delimiters | type { ( [ $ on a selection |
The history stack survives programmatic insertions (symbols, snippets, voice dictation),
which is where browser-native undo typically breaks.
Security model
Security is treated as a correctness property. The following invariants are enforced in the engine and exercised by the test suite where applicable:
| Invariant | Mechanism |
|---|---|
| Page identifiers cannot escape the events directory. | Regex-validated identifiers (UUID-shaped) reject path traversal at the EventStore boundary. |
| Uploaded filenames cannot escape the vault. | The filename is reduced to its basename before reaching the filesystem. |
| Uploads are bounded. | Hard ceiling of 100 MB; oversized requests return 413. |
| User code cannot block the kernel indefinitely. | A monotonic deadline triggers interrupt_kernel(); the error is reported back to the cell. |
| The document title cannot inject LaTeX during export. | All ten LaTeX-meta characters are escaped before reaching \title{...}. |
| Cell figures cannot be served from outside the figure root. | Paths are resolved against figures_dir and rejected if they escape. |
| Vault metadata cannot be corrupted by concurrent writes. | meta.json is written via tempfile and atomic rename. |
| Corrupted events do not brick a page. | Invalid JSONL lines are skipped at read time and the rest of the log is recovered. |
Testing & quality
# Backend
pytest -q
ruff check .
mypy .
bandit -r src/lablog tests -ll -x .venv,prototypes,dist,ui
# Frontend
cd ui
npx tsc --noEmit
npm run build
npm run lint
npm test -- --run
The project ships with 130+ backend tests covering the parser, projector, event store, vault, code engine, snippets, symbols, and the public API, with a coverage threshold of 80%. The frontend is type-checked under strict TypeScript, linted with oxlint, tested with Vitest and built with Vite.
Publishing to GitHub Pages
lablog exports a static, KaTeX-rendered version of the notebook for public sharing.
The interactive surface (editor, executable cells, voice) remains local.
source .venv/bin/activate
uv run python - <<'PY'
from lablog.exporter import export_site
export_site()
PY
A workflow at .github/workflows/pages.yml reproduces the
same export in CI and publishes it on every push to main. To enable it on a fork:
- In Settings → Pages, set the source to GitHub Actions.
- Push to
main. - The deployment URL appears in the workflow summary.
Roadmap
| Milestone | Status |
|---|---|
| Event-sourced engine with deterministic projection | Done |
| Voice → intent → LaTeX pipeline | Done |
| Structural LaTeX renderer (sections, lists, environments) | Done |
| Executable cells with kernel timeout | Done |
| Vault with previews and time-locked deletion | Done |
| Editor: find & replace, undo / redo, cursor-aware insertion | Done |
| Static export & GitHub Pages deployment | Done |
| Native desktop app (offline, pywebview) | Done |
| Time-travel: scrub and restore any point of a page's history | Done |
| Full LaTeX mode: raw compilation, templates menu, error-to-line jump | Done |
| Version diff inside the time-travel panel | Done |
| Portable PyInstaller bundle | Beta |
| In-app PDF compilation with line-aware error reporting | Done |
LaTeX autocomplete + physics templates + lablog new --template | Done |
Multi-file includes (\input{page:…}) at compile time | Done (minimal) |
| Diagram presets + Jupyter / optional PySpice | Done (0.3.0) |
| Re-apply params, dual highlight, .ipynb export | Done (0.3.0) |
| UI profiles + configurable shortcuts | Done (0.3.0) |
| BibTeX / full citeproc | Planned |
| Section and equation cross-references | Planned |
| P2P collaboration and device sync | Exploratory |
Citing
If lablog supports work that leads to publication, the recommended citation is:
@software{labarca_lablog,
author = {Labarca Baeza, José},
title = {{lablog}: a live LaTeX laboratory notebook for working scientists},
year = {2026},
version = {0.3.0},
url = {https://github.com/kegouro/lablog},
note = {Part of the Pharos Project}
}
Also see CITATION.cff.
Contributing
See CONTRIBUTING.md. Security reports: SECURITY.md. Community standards: CODE_OF_CONDUCT.md.
License
lablog is released under the MIT License.
Acknowledgements
lablog is part of the Pharos Project — an effort to lower the barrier of entry
to scientific and educational infrastructure. Identity, logo and banner by
José Labarca Baeza. Original idea conceived with Vicente Muñoz Tolosa.
USM · Valparaíso · Chile · built to let the science flow
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jose_labarca_lablog-0.3.0.tar.gz.
File metadata
- Download URL: jose_labarca_lablog-0.3.0.tar.gz
- Upload date:
- Size: 43.3 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
99e2c3233e047cd965114a33cce48aaf79c860e45c10820ce39c6b3fb9fda200
|
|
| MD5 |
a88e12c42541c098d6c1ef590bedc0ce
|
|
| BLAKE2b-256 |
9fe7fc0947f060c1ba67b0d3e778d4b8080578b927a3d00b7793c91f5e397bc5
|
Provenance
The following attestation bundles were made for jose_labarca_lablog-0.3.0.tar.gz:
Publisher:
release.yml on kegouro/lablog
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jose_labarca_lablog-0.3.0.tar.gz -
Subject digest:
99e2c3233e047cd965114a33cce48aaf79c860e45c10820ce39c6b3fb9fda200 - Sigstore transparency entry: 2139749054
- Sigstore integration time:
-
Permalink:
kegouro/lablog@0845d42138d308677a67174545e519caa7a47895 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/kegouro
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0845d42138d308677a67174545e519caa7a47895 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file jose_labarca_lablog-0.3.0-py3-none-any.whl.
File metadata
- Download URL: jose_labarca_lablog-0.3.0-py3-none-any.whl
- Upload date:
- Size: 1.3 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
50d84f9f3d122b2825a72eab7064c7e5d497117e9e654a939eb0abcb87e8155b
|
|
| MD5 |
a44bb3361c9adc9dab7246f656c46c7b
|
|
| BLAKE2b-256 |
667dbce1c11a07479292c0415ef142fb35cfc2b0873b8ddc6fad6efd582b0044
|
Provenance
The following attestation bundles were made for jose_labarca_lablog-0.3.0-py3-none-any.whl:
Publisher:
release.yml on kegouro/lablog
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jose_labarca_lablog-0.3.0-py3-none-any.whl -
Subject digest:
50d84f9f3d122b2825a72eab7064c7e5d497117e9e654a939eb0abcb87e8155b - Sigstore transparency entry: 2139749062
- Sigstore integration time:
-
Permalink:
kegouro/lablog@0845d42138d308677a67174545e519caa7a47895 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/kegouro
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0845d42138d308677a67174545e519caa7a47895 -
Trigger Event:
workflow_dispatch
-
Statement type: