Skip to content
Merged
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
9 changes: 8 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,15 @@ jobs:
uses: actions/setup-node@v6
with:
node-version: "20"
cache: "npm"

- name: Syntax-check plugin source
- name: Install dependencies
run: npm ci

- name: Type-check & build (tsc --noEmit + esbuild)
run: npm run build

- name: Syntax-check bundled output
run: node --check main.js

- name: Validate manifest, versions & required files
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ jobs:
uses: actions/setup-node@v6
with:
node-version: "20"
cache: "npm"

- name: Install dependencies
run: npm ci

- name: Type-check & build (tsc --noEmit + esbuild)
run: npm run build

- name: Validate
run: |
Expand Down
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@
.idea/
.vscode/

# Node (only relevant if a build/tooling step is ever added)
# Node / build tooling
node_modules/
npm-debug.log*
*.tsbuildinfo

# Build output — main.js is bundled from src/ by esbuild (not committed).
# It is rebuilt in CI and attached to releases. Run `npm run build` locally.
main.js
main.js.map

# Obsidian test vault artifacts
data.json
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

_Nothing yet._

## [1.1.4] - 2026-06-24

### Fixed

- Full-screen tables now match the inline view's styling. The clone is opened
outside the note's `.markdown-rendered` context, which dropped the theme's
table borders/padding/header background; it is now re-wrapped in that context
(with `display: contents` so the centering/scroll layout is unchanged).
- Diagram host overflow is now neutralized on the actual `.mermaid` wrapper (via
`closest('.mermaid')`), so the override applies even when the diagram svg is
nested or matched only by its `mermaid-*` id. No user-visible change in the
common case; removes a latent edge case.

### Internal

- Removed the remaining `!important` declarations in `styles.css`, overriding
Obsidian's styles by selector specificity instead.
- Migrated the project to **TypeScript + esbuild** (the official
`obsidian-sample-plugin` toolchain): source now lives in `src/main.ts` and is
bundled to `main.js` (a build artifact, no longer committed). CI and the
release workflow type-check (`tsc --noEmit`) and build before packaging. No
change to the shipped plugin's behavior.
- Followed Obsidian's code guidelines: icons are now built via the DOM
(`createElementNS`) instead of `innerHTML`.
- Added a headless-browser E2E test (`tests/e2e/`, `npm run test:e2e`) that
drives the bundled plugin and guards the full-screen table styling.
- Restructured docs: `README.md` is now user-facing; developer setup and
architecture moved to `docs/DEVELOPMENT.md`.

## [1.1.3] - 2026-06-23

### Changed
Expand Down
70 changes: 70 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

Lookout is an **Obsidian plugin** for surveying wide content: pan/zoom Mermaid diagrams, fit-to-frame, and open diagrams or wide tables full-screen. It is written in **TypeScript** (`src/main.ts`) and bundled to `main.js` by **esbuild** (the official `obsidian-sample-plugin` toolchain). `main.js` is **build output** — gitignored, rebuilt in CI, attached to releases — so **never edit `main.js`; edit `src/main.ts` and `styles.css`.** Obsidian loads `main.js`/`manifest.json`/`styles.css` from the plugin folder **root**, so esbuild emits the bundle there and **there is no `dist/`**. `tsc` is used **only to type-check** (`--noEmit`); esbuild does the transpile/bundle. The only runtime dependency is `obsidian` (host-provided, marked `external`).

## Commands

```bash
npm ci # install dev deps (first time / CI)
npm run dev # esbuild watch: rebuild main.js on save (local dev)
npm run build # tsc --noEmit (type-check) + esbuild production bundle
npm run validate # manifest/versions consistency + required files
```

The full CI gate (run before every PR), on Node 20:

```bash
npm ci
npm run build # type-check + bundle -> main.js
node --check main.js
node scripts/validate.mjs
```

There are no unit tests — behavioural verification is manual in a real vault (see below). `tsconfig.json` is `strict` with `strictPropertyInitialization` off (fields init in `_build()`); see `docs/DEVELOPMENT.md`.

## Local development against a vault

Obsidian runs the plugin from `<vault>/.obsidian/plugins/lookout/`. Symlink the repo there and run the watch build; reload the plugin (toggle off/on, or the *Reload app without saving* command) after each rebuild:

```bash
ln -s "$(pwd)" /path/to/test-vault/.obsidian/plugins/lookout
npm run dev # rebuilds main.js on every save
```

A note containing a wide Mermaid diagram and a wide table exercises both features. **Always test in both Reading view and Live Preview** — they render content into different DOM containers and have historically diverged (see the table-processing guards in `src/main.ts`).

## Architecture

Everything lives in `src/main.ts` (~900 lines), three classes:

- **`LookoutPlugin`** (the `default` export) — lifecycle and **discovery**. It finds rendered Mermaid `<svg>`s (`.mermaid svg, svg[id^="mermaid-"]`) and `<table>`s anywhere in the document and wraps each in a view. Discovery is driven by several overlapping triggers because Mermaid renders asynchronously and Obsidian re-renders panes on navigation: `onLayoutReady`, workspace events (`layout-change`, `active-leaf-change`, `file-open`), a `registerMarkdownPostProcessor`, and a `MutationObserver` on `document.body`. All of these funnel through `queueScan()` (debounced ~120ms) → `scan()` → `scanWithin(root)` → `process()` / `processTable()`.
- **`DiagramView`** — one pan/zoom controller per diagram. The same class serves both the **inline** frame (wraps the live svg in place) and **full-screen** (wraps a *clone* in a fixed overlay). It owns the transform math: `_measure`, `fit`, `actualSize`, `zoomTo`/`zoomBy`, `_panBounds`/`_clampPan`, and `_render`.
- **`TableView`** — far simpler; tables keep native horizontal scroll and only gain a full-screen button. Full-screen clones the table into a maximized scroll overlay.

### Idempotent processing (important invariant)

Because discovery fires repeatedly on the same DOM, every processor must be **idempotent**. Each handled element is stamped with the `PROCESSED` attribute and re-skipped; processors also bail early if the element is already inside Lookout's own wrappers (`.lookout-viewport`, `.lookout-fs`, `.lookout-table-host`). When adding any new element discovery, preserve this stamp-and-skip pattern or scans will duplicate views.

`processTable` carries hard-won guards: it enhances tables under `.markdown-rendered` **or** `.markdown-source-view` (Live Preview renders tables as a CM widget), but skips any table containing `[contenteditable="true"]` — that is the table the user is actively editing, whose DOM Obsidian owns.

### DOM ownership and CSS specificity

Lookout moves Obsidian-owned nodes (the svg, the table) into its own wrappers and must restore them on `destroy()`/`onunload`. When overriding Obsidian's built-in styles in `styles.css`, **win on selector specificity rather than `!important`** — e.g. qualify with the host class (`.mermaid.lookout-host`) or scope under a container. The codebase deliberately avoids `!important`.

## Conventions

- **Keep the dependency surface minimal.** `obsidian` is the only runtime dependency (host-provided, `external` — never bundled). Don't add runtime dependencies without a strong reason; dev dependencies stay limited to the TypeScript/esbuild toolchain. Type with the real Obsidian types, and keep `tsc --noEmit` (CI) green.
- **Follow Obsidian's code guidelines.** No `innerHTML`/`outerHTML` — build DOM nodes via the API (icons are constructed with `createElementNS` in `svgIcon()`). Clean up listeners/observers/timers on `destroy()`/`onunload()`.
- **Visual language is fixed:** quiet Obsidian theme surfaces (`var(--background-*)`, `var(--text-*)`) plus a single survey-cyan accent (`--lookout-accent`) reserved for focus and the active zoom gauge. Do not introduce new colors. Icons are inline lucide-style SVG (1.75px stroke) built via `svgIcon()`.
- User-facing strings (command names, `Notice` text) are in **Korean**.
- Respect `prefers-reduced-motion` (the `REDUCED_MOTION` flag / the reduced-motion media block) when adding animation.

## Branching & releases

- Default branch is **`dev`** — branch off it (`feat/*`, `fix/*`, `docs/*`, `chore/*`) and open PRs against `dev`, never `main`. Use Conventional Commits.
- `main` only receives merges from `dev` or `hotfix/*`. **Releases are automatic on merge to `main`**: the workflow reads `version` from `manifest.json` and pushes a bare-version tag (no `v` prefix — Obsidian convention).
- A version bump must touch three files in lockstep (validated by `scripts/validate.mjs`): `manifest.json` (`version`), `versions.json` (`"<version>": "<minAppVersion>"`, which must equal `manifest.minAppVersion`), and `CHANGELOG.md` (move `Unreleased` notes into a dated section).
22 changes: 14 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,26 +67,31 @@ repository instead of a fork.

## Local development

Lookout ships as **plain CommonJS with no build step** — Obsidian loads
`main.js` directly. To develop against a real vault:
Lookout is written in **TypeScript** (`src/main.ts`) and bundled to `main.js`
by **esbuild**. To develop against a real vault:

1. Use a throwaway test vault (not your real notes).
2. Symlink (or copy) the repo into the vault's plugins folder:
2. Install dependencies: `npm ci`.
3. Symlink (or copy) the repo into the vault's plugins folder:
```bash
ln -s "$(pwd)" /path/to/test-vault/.obsidian/plugins/lookout
```
3. In Obsidian, enable **Lookout** under *Settings → Community plugins*.
4. Edit `main.js` / `styles.css`, then reload the plugin (toggle it off/on, or
use the *Reload app without saving* command) to see your changes.
4. Start the watch build: `npm run dev` (re-bundles `main.js` on every save).
5. In Obsidian, enable **Lookout** under *Settings → Community plugins*.
6. Edit `src/main.ts` / `styles.css`, then reload the plugin (toggle it off/on,
or use the *Reload app without saving* command) to see your changes.

A note with a wide Mermaid diagram and a wide table is enough to exercise both
features in Reading view and Live Preview.
features in Reading view and Live Preview. See
[docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for the full developer guide.

## Before you open a PR

Run the same checks CI runs:

```bash
npm ci
npm run build # tsc --noEmit (type-check) + esbuild bundle
node --check main.js
node scripts/validate.mjs
```
Expand All @@ -95,7 +100,8 @@ Please also:

- Keep the **drafting / survey-instrument** visual language (quiet Obsidian
theme surfaces, a single survey-cyan accent). Avoid introducing new colors.
- Match the surrounding code style (plain CommonJS, no new dependencies).
- Follow Obsidian's code guidelines (no `innerHTML`; clean up on teardown) and
avoid adding runtime dependencies.
- Update `README.md` / docs when behavior changes.

## Releasing (maintainers)
Expand Down
107 changes: 51 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,99 +6,94 @@

> Survey wide content instead of scrolling sideways.

Lookout is an [Obsidian](https://obsidian.md) plugin for reading **wide Mermaid diagrams and wide tables**. Instead of squinting through a note's narrow horizontal scrollbar, you can pan and zoom a diagram, fit it to the frame, or throw either a diagram or a table up full‑screen.
**Lookout** is an [Obsidian](https://obsidian.md) plugin for reading **wide
Mermaid diagrams and wide tables**. Instead of squinting through a note's narrow
horizontal scrollbar, pan and zoom a diagram, fit it to the frame, or throw
either a diagram or a table up full-screen.

The controls follow a quiet "drafting / survey instrument" visual language: they use Obsidian's own theme surfaces, with a single survey‑cyan accent reserved for focus and the active zoom gauge.
The controls follow a quiet *drafting / survey-instrument* visual language: they
borrow Obsidian's own theme surfaces, with a single survey-cyan accent reserved
for focus and the active zoom gauge.

## Features

### Mermaid diagrams

Each rendered Mermaid diagram gets an unobtrusive instrument toolbar (it fades in on hover):
Each rendered Mermaid diagram gets an unobtrusive instrument toolbar (it fades in
on hover):

- **Pan** — drag, or scroll with the mouse wheel (Shift maps a vertical wheel to horizontal). At an edge with nowhere to go, scrolling releases back to the page.
- **Zoom** — `Ctrl`/`Cmd` + wheel zooms toward the cursor, or use the `+` / `−` buttons.
- **Gauge** — a monospace readout shows the current zoom; click it to snap back to **100 %**, top‑left.
- **Pan** — drag, or scroll with the mouse wheel (Shift maps a vertical wheel to
horizontal). At an edge with nowhere to go, scrolling releases back to the page.
- **Zoom** — `Ctrl` / `Cmd` + wheel zooms toward the cursor, or use the `+` / `−`
buttons.
- **Gauge** — a monospace readout shows the current zoom; click it to snap back to
**100 %**, top-left.
- **Fit to frame** — scales the whole diagram to the frame.
- **Full screen** — opens the diagram in a focused full‑screen canvas with the same controls.
- **Full screen** — opens the diagram in a focused full-screen canvas with the
same controls.

The inline frame hugs the diagram's natural (100 %) height, so a fully visible diagram has no dead space; tall diagrams are capped at 70 % of the window height and pan.
The inline frame hugs the diagram's natural (100 %) height, so a fully visible
diagram has no dead space; tall diagrams are capped at 70 % of the window height
and pan.

### Tables

Wide tables keep their normal horizontal scroll but gain a single **full‑screen** button (no zoom) pinned to the visible top‑right corner. Full screen shows the table in a maximized scroll area where its full width fits far better than in the note's narrow reading column.
Wide tables keep their normal horizontal scroll but gain a single **full-screen**
button (no zoom) pinned to the visible top-right corner. Full screen shows the
table in a maximized scroll area where its full width fits far better than in the
note's narrow reading column.

Tables are enhanced in both **Reading view** and **Live Preview**; the table you are actively editing is left untouched.
Tables are enhanced in both **Reading view** and **Live Preview**; the table you
are actively editing is left untouched.

## Keyboard
## Keyboard & commands

When a diagram frame is focused (or in full screen):

| Key | Action |
| --- | --- |
| `+` / `=` | Zoom in |
| `−` / `_` | Zoom out |
| `0` | Reset to 100 %, topleft |
| `0` | Reset to 100 %, top-left |
| Arrow keys | Pan |
| `F` | Open full screen (inline only) |
| `Esc` | Close full screen |

A command, **"Open the active note's first Mermaid diagram full screen"**, is also available from the command palette and can be bound to a hotkey.
A command, **“Open the active note's first Mermaid diagram full screen”**, is
available from the command palette and can be bound to a hotkey.

## Installation

Lookout works on both desktop and mobile (Obsidian **1.0.0+**).

### Manual

1. Download `main.js`, `manifest.json`, and `styles.css` from the [latest release](https://github.com/Post-Math/Lookout/releases).
2. Create a folder `lookout` under your vault's `.obsidian/plugins/` directory and place the three files inside it.
1. Download `main.js`, `manifest.json`, and `styles.css` from the
[latest release](https://github.com/Post-Math/Lookout/releases).
2. Create a folder `lookout` under your vault's `.obsidian/plugins/` directory and
place the three files inside it.
3. Reload Obsidian and enable **Lookout** under *Settings → Community plugins*.

### BRAT

Add `Post-Math/Lookout` as a beta plugin with the [BRAT](https://github.com/TfTHacker/obsidian42-brat) plugin.

## Development

Lookout ships as plain CommonJS with **no build step** — Obsidian loads `main.js` directly. To work on it, clone this repo (or symlink it) into a vault's `.obsidian/plugins/lookout/` folder and edit `main.js` / `styles.css` directly, then reload the plugin.

```
main.js # the plugin (DiagramView, TableView, LookoutPlugin)
styles.css # the drafting/survey-instrument UI
manifest.json # plugin metadata
versions.json # plugin version -> minimum Obsidian version
scripts/validate.mjs # CI checks (manifest/versions consistency, required files)
```

Run the same checks CI runs before opening a PR:

```bash
node --check main.js
node scripts/validate.mjs
```

## Branching model

Lookout uses a git-flow style model:

- **`main`** — release / deployment. Always reflects the latest published release. Protected; only receives merges from `dev` (and `hotfix/*`).
- **`dev`** — integration branch and the **default branch**. The base for all day-to-day work.
- **`feat/*`, `fix/*`, `docs/*`, `chore/*`** — short-lived topic branches, branched off `dev` and merged back into `dev`.
### BRAT (beta)

```
feat/my-change ──▶ dev ──▶ main ──▶ tag (e.g. 1.2.0) ──▶ Release
```
Add `Post-Math/Lookout` as a beta plugin with the
[BRAT](https://github.com/TfTHacker/obsidian42-brat) plugin to track pre-release
builds.

Always branch off `dev` and open pull requests against `dev` — never `main`.
## Support

## Contributing
Found a bug or have an idea? Please use the
[issue tracker](https://github.com/Post-Math/Lookout/issues). For security
reports, see [SECURITY.md](SECURITY.md).

Contributions are welcome! External contributors work via a **fork** and open
pull requests against `dev`. A [code owner](.github/CODEOWNERS) review and a
green CI run are required before a PR can merge.
## Contributing & development

Please read **[CONTRIBUTING.md](CONTRIBUTING.md)** for the full workflow, and our
**[Code of Conduct](CODE_OF_CONDUCT.md)**. For security issues, see
**[SECURITY.md](SECURITY.md)**.
Contributions are welcome! Start with **[CONTRIBUTING.md](CONTRIBUTING.md)** for
the workflow and our [Code of Conduct](CODE_OF_CONDUCT.md), and see
**[docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)** for the local setup, the checks to
run, and an architecture overview. In short: Lookout is written in **TypeScript**
(`src/main.ts`) and bundled to `main.js` with **esbuild** — `npm ci`, then
`npm run dev` to watch-build while you edit.

## License

Expand Down
Loading