Fork of DanielCheer/obsidian-web-viewer (MIT, 2026-04) with Lukas' first additions for the Hermes Wiki workflow. Additive customization layer (vault-custom.js) — keeps upstream-merge trivial via a one-line script tag in vault.html. Features added: 1. Current-page highlighting in 3D Graph — current node gold+glow, connected nodes light-blue, others dim to 20% opacity 2. Click-to-navigate on graph — Three.js raycaster triggers file load 3. Responsive layout — graph collapses below 1024px, tree below 768px 4. URL-based deep-linking via ?file=<path> query param 5. Server-side ?file= support in /api/vault/file/ endpoint Modified files: - server.py: +7 lines (Lukas-add: ?file= query param parsing) - vault.html: +1 line (script tag for vault-custom.js) New files: - vault-custom.js: 11KB, all customizations in one place under HermesCustom namespace - README.md: fork intro, quick start, customization guide - CHANGELOG.md: Lukas-additions tracking - docs/ARCHITECTURE.md: design rationale - docs/CUSTOMIZATIONS.md: feature spec - .gitignore: standard 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
5.5 KiB
Customizations — Feature Spec
Each section describes one Lukas-feature: what it does, how it works, where the code lives, and known limitations.
1. Current-page highlighting in 3D Graph
What: When you open a file, the corresponding node in the 3D Graph becomes brighter and larger. Nodes that the open file links to (via [[wikilinks]]) also brighten. All other nodes dim.
How:
HermesCustom.getCurrentFile()reads the file path from?file=URL param (preferred) or tracks via theloadFile()hookHermesCustom.getConnectedFiles(currentFile)looks up outgoing edges in the global graph dataHermesCustom.highlightCurrentNode()iterateswindow.graphNodeObjects(obv's Three.js node meshes) and adjustsmaterial.color,emissive,scale,opacity
Code: vault-custom.js, methods under namespace HermesCustom.*.
Materials used (Catppuccin palette, matches theme):
- Current:
#FFD700(gold) + emissive glow + 1.5x scale - Connected:
#89b4fa(light blue) + faint emissive + 1.1x scale - Other:
#313244(muted) + 20% opacity (transparent)
Known limitation:
- Uses
window.graphNodeObjectsglobal from obv — if obv renames this, we need to update. Mitigation: detection inhighlightCurrentNodewaits up to 500ms for obv's graph to be ready. - Highlight doesn't preserve selection across page reload — fresh fetch on every load.
2. Click-to-navigate on 3D Graph
What: Clicking a node in the 3D Graph navigates to that file's page.
How:
HermesCustom.setupGraphClickHandler()sets up a Three.js Raycaster on the canvas DOM element- On click, computes normalized mouse coords, raycasts into scene
- First intersection's
userData.nodeIdis the file path - Calls
window.loadFile(nodeId)(obv's existing loader) - Updates URL with
window.history.pushState({}, '', '?file=<path>')
Code: vault-custom.js, ~25 lines.
Known limitation:
- Three.js raycasting requires nodes to have
userData.nodeIdset — verified in obv'saddNodeToScenefunction (line ~310 in obv's graph render). If obv removes this, our handler silently does nothing. - No keyboard navigation in graph (yet). Roadmap item.
3. Responsive Layout
What: The 3-panel desktop layout collapses gracefully on smaller screens.
Breakpoints:
- Desktop (≥1024px): Full 3-panel layout — Tree 240px | Content 1fr | Graph 240px
- Tablet (768px–1023px): Tree 240px | Content 1fr | Graph 200px (bottom panel)
- Mobile (<768px): Tree as hamburger menu (off-canvas, slides in from left) | Content full-width | Graph hidden
How:
HermesCustom.injectResponsiveCSS()adds<style>tag with media queriesHermesCustom.responsiveLayout()adds.mobile-mode/.tablet-modeclasses to.vault-appbased onwindow.innerWidth- Resize handler is throttled (150ms) to avoid jank
- Hamburger toggle button is appended to header in mobile mode
Code: vault-custom.js, ~80 lines (including CSS string).
Known limitation:
- The injected CSS uses
!importantto override obv'sgrid-template-columns. If obv restructures the layout, we may need to adjust selectors. - Hamburger menu doesn't auto-close on file selection — minor UX nit. Roadmap.
4. URL-based navigation (?file=)
What: The URL contains the currently-open file as a ?file=<path> query param. This makes pages deep-linkable and back-button friendly.
How:
HermesCustom.getCurrentFile()reads?file=fromURLSearchParamsvault-custom.jsupdates URL viahistory.pushState()on file load- Server.py patch accepts
?file=in API calls
Server-side patch (server.py):
# Lukas-add: also support ?file=<path> query for clean URL navigation
if "?" in rel:
rel, _, query = rel.partition("?")
from urllib.parse import parse_qs
file_param = parse_qs(query).get("file", [None])[0]
if file_param:
rel = file_param
Known limitation:
- Server-side patch assumes
?file=is the only query parameter obv might add. If obv adds another (e.g.?theme=), the simplepartition("?")still works but is naive. Acceptable for current state.
5. Tracking current file via loadFile hook
What: vault-custom.js patches obv's window.loadFile to remember the last loaded path. Used as a fallback when the URL doesn't have ?file= (e.g., when obv opens a file from search without updating URL).
How:
const originalLoadFile = window.loadFile;
window.loadFile = function(path) {
HermesCustom._lastLoadedFile = path;
const result = originalLoadFile.apply(this, arguments);
setTimeout(HermesCustom.highlightCurrentNode, 300);
return result;
};
Known limitation:
- The 300ms delay assumes the render finishes within that time. Slow devices may need a longer delay. Could be improved by hooking into a render-completion signal if obv provides one.
Testing
Manual testing only for now. Test cases:
| Scenario | Expected |
|---|---|
Open concepts/agent-reference-model.md |
Graph: that node gold + glow, connected nodes light blue, others dim |
| Open file via search (not via tree click) | Same highlighting (URL gets ?file=) |
| Click graph node | File loads, URL updates |
| Resize browser to <1024px | Graph moves to bottom panel |
| Resize browser to <768px | Tree becomes hamburger, graph hidden |
| Hard reload on file URL | File loads directly (server.py parses ?file=) |
| Upstream obv update (theoretical) | Only 1 line of conflict in vault.html, easy merge |
Planned: Playwright-based automated test for the 5 visual features.