Initial import of LogDarioSeq baseline
Establishes version control for the personal Logseq DB fork before starting the server-client architecture migration and domain features described in docs/agent-guide and the approved implementation plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
195
docs/adr/0017-url-target-graph-resolution.md
Normal file
195
docs/adr/0017-url-target-graph-resolution.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# ADR 0017: URL Target Graph Resolution
|
||||
|
||||
Date: 2026-05-20
|
||||
Status: Accepted
|
||||
|
||||
## Context
|
||||
Logseq URLs can point at a graph, page, block, or file. Today the graph target is
|
||||
not carried through one coherent flow.
|
||||
|
||||
The custom protocol path is handled in Electron main:
|
||||
- `logseq://graph/<graph>` and `logseq://new-window/<graph>` are parsed in
|
||||
`electron.url/local-url-handler`.
|
||||
- Electron resolves `<graph>` through `electron.handler/get-graph-name`.
|
||||
- If no window is already associated with that graph, Electron asks the current
|
||||
renderer to run `openNewWindowOfGraph`.
|
||||
- The renderer then opens a new Electron window with `#/?graph=<repo>`.
|
||||
|
||||
This depends on renderer IPC timing and renderer-local storage. On first app
|
||||
launch, the initial deeplink is handled soon after the main window is created,
|
||||
while the renderer listener can still be unavailable. When the message is lost,
|
||||
the newly opened app falls back to the stored `:git/current-repo`, so the URL
|
||||
appears to always open the current graph.
|
||||
|
||||
The web app has a related weakness. `frontend.state` only reads `:graph` from
|
||||
`frontend.util/parse-params` during initial state creation, and that parser only
|
||||
understands hash fragments shaped like `#/?graph=...`. Page/block web URLs and
|
||||
later hash changes can route inside the currently opened graph instead of first
|
||||
switching to the graph named by the URL.
|
||||
|
||||
Graph names are not stable identifiers. Users can rename graphs, and two linked
|
||||
graphs can share the same short basename. URLs that are meant to survive
|
||||
renames, browser sessions, and device handoffs need to identify the graph by id.
|
||||
|
||||
## Decision
|
||||
Make URL graph selection an explicit target object and process it before route
|
||||
navigation in the renderer, with a small persisted graph registry used to turn
|
||||
stable graph ids into local repos.
|
||||
|
||||
1. Introduce a shared URL target model for Electron protocol URLs, web URLs, and
|
||||
mobile URLs:
|
||||
- `:graph-id` - canonical graph id from the URL.
|
||||
- `:graph-identifier` - compatibility alias, such as a short graph name or
|
||||
full DB repo name.
|
||||
- `:repo` - resolved full local repo name, for example `logseq_db_work`.
|
||||
- `:route` - one of graph home, page, block, or file.
|
||||
- `:open-mode` - current window or new window.
|
||||
- `:source` - protocol URL, web URL, startup argv, or second instance.
|
||||
2. Use `:graph-id` as the canonical URL identity. For remote graphs,
|
||||
`:graph-id` is the remote graph UUID. For local-only graphs, `:graph-id` is
|
||||
the local graph UUID. Generated URLs should include `graph-id` whenever the
|
||||
graph id is known.
|
||||
3. Resolve URL graph targets through one graph resolver that accepts, in order:
|
||||
- graph ids from the local graph registry;
|
||||
- full DB repo names, such as `logseq_db_work`;
|
||||
- short local graph names, such as `work`;
|
||||
- existing canonicalized DB repo names.
|
||||
4. Treat an unresolved graph id as an unresolved URL target, not as a successful
|
||||
match for the current graph. Mobile reports this to the user. Web startup
|
||||
currently logs the unresolved target and continues with the tab-local or
|
||||
stored graph so the app can still boot.
|
||||
5. For Electron custom protocol URLs, the main process owns URL intake and
|
||||
graph-name/graph-id resolution. This branch keeps the existing
|
||||
renderer-mediated window creation path for custom protocol URLs and teaches
|
||||
it to resolve graph ids through the registry.
|
||||
6. For web and mobile URLs, the renderer owns URL intake and graph resolution.
|
||||
It must switch to the target graph before applying the route target.
|
||||
7. Once the target graph is loaded, apply the route target exactly once.
|
||||
8. Do not store URL graph targets in `:git/current-repo` until the graph switch
|
||||
succeeds.
|
||||
9. Use `sessionStorage` for tab-local graph identity on web reloads that do not
|
||||
carry a URL graph target. This preserves each browser tab's graph without
|
||||
appending `graph-id` to every internal route.
|
||||
|
||||
## Supported URL Shapes
|
||||
Canonical generated web URLs use graph ids:
|
||||
- `https://logseq.com/?graph-id=<graph-uuid>`
|
||||
- `https://logseq.com/page/<page-uuid>?graph-id=<graph-uuid>`
|
||||
- `https://logseq.com/block/<block-uuid>?graph-id=<graph-uuid>`
|
||||
|
||||
Electron protocol URLs accept graph ids through the same graph identifier slot:
|
||||
- `logseq://graph/<graph-uuid>`
|
||||
- `logseq://graph/<graph-uuid>?page=<page-name-or-uuid>`
|
||||
- `logseq://graph/<graph-uuid>?block-id=<block-uuid>`
|
||||
- `logseq://new-window/<graph-uuid>?page=<page-name-or-uuid>`
|
||||
|
||||
Compatibility URLs remain accepted:
|
||||
- `logseq://graph/<graph-name>`
|
||||
- `logseq://graph/<graph-name>?page=<page-name>`
|
||||
- `logseq://graph/<graph-name>?block-id=<uuid>`
|
||||
- `logseq://graph/<graph-name>?file=<file>`
|
||||
- `https://logseq.com/#/?graph=<graph-name>`
|
||||
- `https://logseq.com/#/page/<page-name>?graph=<graph-name>`
|
||||
|
||||
Generated web URLs should use path routes instead of hash routes. The app may
|
||||
continue accepting hash-routed URLs during migration, but new share/copy-link
|
||||
helpers should emit path URLs with `graph-id`.
|
||||
|
||||
## Graph Registry
|
||||
URL graph-id resolution must not open every graph database during startup. Keep a
|
||||
small graph registry outside each graph DB.
|
||||
|
||||
1. Web and mobile store the graph registry in IndexedDB.
|
||||
2. Electron stores the graph registry in
|
||||
`(.getPath app "home")/.logseq/graphs.edn`.
|
||||
3. Registry entries include at least:
|
||||
- `:graph-id`
|
||||
- `:repo`
|
||||
- `:graph-name`
|
||||
- `:local-graph-id`
|
||||
- `:updated-at`
|
||||
4. Do not persist a separate `:rtc-graph-id` in the registry. For remote graphs,
|
||||
`:graph-id` is the remote graph UUID. For local-only graphs, `:graph-id` is
|
||||
the local graph UUID.
|
||||
5. Update the registry whenever a graph is created, opened, imported,
|
||||
downloaded, uploaded to RTC, renamed, unlinked, or deleted.
|
||||
6. If a registry lookup fails for a graph id, the app may run an explicit repair
|
||||
path that enumerates local graph storage and opens graph DBs to rebuild the
|
||||
registry. This repair path must not be the normal URL-open path.
|
||||
|
||||
## Electron Runtime Design
|
||||
1. Store the registry at `(.getPath app "home")/.logseq/graphs.edn`.
|
||||
2. Resolve Electron protocol graph identifiers through that registry before
|
||||
falling back to existing repo/name lookup.
|
||||
3. For shift-click in the graph list, open a new Electron window through the
|
||||
existing `openNewWindow` IPC using the resolved repo. Electron windows still
|
||||
use repo identity for window creation.
|
||||
4. Keep a per-window pending target in Electron state only for work that cannot
|
||||
be represented safely in the URL, such as a file redirect that requires graph
|
||||
readiness. Avoid a single global `:window/once-graph-ready` callback because
|
||||
concurrent windows can overwrite each other.
|
||||
5. When the renderer reports `graphReady`, Electron should look up and consume
|
||||
the pending target for that specific window id.
|
||||
6. Existing error notifications for missing or unknown graphs remain, but they
|
||||
should be sent to the selected/fallback window after graph resolution fails.
|
||||
7. Direct main-process creation of route-aware graph windows remains follow-up
|
||||
work; it should replace the renderer-mediated `openNewWindowOfGraph` path.
|
||||
|
||||
## Web Runtime Design
|
||||
1. Add a parser that can read graph targets from path URLs and compatibility
|
||||
hash URLs.
|
||||
2. During frontend startup:
|
||||
- parse the URL target;
|
||||
- resolve `:graph-id` through the IndexedDB graph registry;
|
||||
- resolve compatibility `:graph-identifier` against the known graph list when
|
||||
no `:graph-id` is present;
|
||||
- initialize from the resolved URL target when it resolves;
|
||||
- otherwise use the tab-local `sessionStorage` graph target, then the stored
|
||||
`:git/current-repo`, then the first linked repo.
|
||||
3. During startup, apply resolved page/block URL targets after the target graph
|
||||
is restored. Later in-app route changes with graph targets are follow-up
|
||||
work.
|
||||
4. During route generation:
|
||||
- generated page routes include `graph-id` when the current graph id is
|
||||
available, but startup/render-phase redirects tolerate a temporarily
|
||||
unavailable graph id instead of crashing.
|
||||
5. Generated web links from `frontend.util.url` should include `graph-id` on the
|
||||
actual route.
|
||||
6. Web "open in another tab" and shift-click from All graphs open `#/?graph-id=`
|
||||
URLs for existing local graphs.
|
||||
|
||||
## Mobile Runtime Design
|
||||
1. Use the same URL target parser and resolver as the web runtime.
|
||||
2. Store and resolve the graph registry from IndexedDB.
|
||||
3. Treat mobile app intents and universal links as URL sources that produce the
|
||||
same target model.
|
||||
|
||||
## Consequences
|
||||
- Resolvable graph-id URLs open the requested graph instead of silently using the
|
||||
last current graph.
|
||||
- Electron protocol URLs can resolve graph ids via the registry, but route-aware
|
||||
direct window creation remains follow-up work.
|
||||
- Web graph URLs become route-aware, so page and block links can switch graph
|
||||
before navigating during startup and mobile deeplink handling.
|
||||
- Web and mobile graph-id resolution is fast because it reads the graph registry
|
||||
instead of scanning OPFS graph directories and opening graph databases.
|
||||
- Electron graph-id resolution is fast because it reads
|
||||
`(.getPath app "home")/.logseq/graphs.edn` instead of opening each graph
|
||||
database.
|
||||
- Bare web reloads preserve the tab's graph through `sessionStorage`, so two
|
||||
tabs can remain on different graphs even when one tab changes the global
|
||||
current graph.
|
||||
- The implementation needs focused tests around URL parsing, graph resolution,
|
||||
first-launch Electron deeplinks, second-instance deeplinks, and web/mobile
|
||||
route changes.
|
||||
|
||||
## Follow-up Work
|
||||
1. Add Electron main tests for first-launch and second-instance deeplinks.
|
||||
2. Add renderer tests proving that web URLs switch graph before page/block
|
||||
navigation after graph switches triggered by later route changes, not only
|
||||
startup URLs.
|
||||
3. Add graph registry read/write tests for IndexedDB and the Electron
|
||||
`(.getPath app "home")/.logseq/graphs.edn` registry.
|
||||
4. Replace `:window/once-graph-ready` with per-window pending targets.
|
||||
5. Replace Electron custom protocol's renderer-mediated `openNewWindowOfGraph`
|
||||
path with direct main-process route-aware window creation.
|
||||
Reference in New Issue
Block a user