From ebf6136d2f37d743e61edb6de72e12a3c4775dcc Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Fri, 29 May 2026 22:46:26 +0200 Subject: [PATCH] fix: blogmark bookmarklet uses bds2:// scheme to avoid legacy bds:// clash --- SPECGAPS.md | 4 ++-- lib/bds/scripting/capabilities/app_shell.ex | 2 +- lib/bds/scripts/transforms.ex | 2 +- specs/script.allium | 3 ++- test/bds/scripting/api_test.exs | 7 +++++++ 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/SPECGAPS.md b/SPECGAPS.md index 5b52373..de8da78 100644 --- a/SPECGAPS.md +++ b/SPECGAPS.md @@ -28,7 +28,7 @@ Gap categories: **SC** = spec correct, fix code | **CS** = code correct, update | A1-14c | ~~Embedding model runs on CPU only; no Apple GPU acceleration~~ | embedding.allium invariant NativeAcceleratedExecution | `Backends.Neural` now selects the defn compiler at serving-build time: Apple GPU via EMLX (MLX/Metal) on arm64 macOS, EXLA-CPU elsewhere | **Resolved:** added `{:emlx, "~> 0.2.0"}` dep (ships precompiled MLX binaries; EMLX 0.2.0 implements both `EMLX.Backend` and the `Nx.Defn.Compiler` behaviour, GPU-default); `Backends.Neural` gained a pure `select_accelerator/3` policy (`:auto` prefers EMLX only when available **and** on Apple Silicon; explicit `:emlx`/`:exla` honoured; forced `:emlx` degrades to EXLA when unavailable so misconfigured hosts still run), `current_accelerator/0`, and `defn_options/1`; `build_serving` places params on `{EMLX.Backend, device: :gpu}` and compiles with `EMLX` for the EMLX path, keeps `EXLA` otherwise; new `accelerator: :auto` config key; spec `NativeAcceleratedExecution` + `EmbeddingModel` updated; PLT app added; 7 tests added (offline — test config still uses the InApp stub). | | A1-15 | ~~Preview vs generation content source strategy undocumented~~ | preview.allium (no invariant), generation.allium (no invariant) | Generation uses only published .md file content (`Generation.Data` snapshots set `content: nil`); preview includes published+draft posts and prefers DB content over file (`Preview.Router` queries `:published`/`:draft`, uses `editor_body`) | **Resolved:** added `PreviewDraftOverlay` invariant to preview.allium and `GenerationPublishedOnly` invariant to generation.allium; both cross-reference each other; code already correct, 3 tests added for draft-in-preview behavior | | A1-16 | ~~Public project content + data_path discovery not compliant with storage-location spec~~ | project.allium `PublicContentLivesInProjectFolder` / `PrivateArtifactsLiveInOsAppDir` / `DataPathNotPersistedInProjectJson` / `DiscoverProjectDataPath` | Public content now lives under a per-user default content location, never the repo | **Resolved:** `project_data_dir/1` drops the `priv/data/projects/` repo fallback — a project without an explicit `data_path` resolves to `default_content_root()/` (configurable via `:default_content_root`, else `~/bds`), never the repo or `private_dir`; the `default` project is now created on first launch with an explicit `data_path` under that location and its folder is `mkdir`'d (`PublicContentLivesInProjectFolder`); added `Projects.private_dir/0`, `default_content_root/0`, and a machine-local project registry (`registry_path/0` → `project_registry.json` under `private_dir`, written on create/ensure-default, removed on delete) that remembers each project's folder without embedding it in `meta/project.json` (`DataPathNotPersistedInProjectJson`/`DiscoverProjectDataPath` — already satisfied since `project.json` never serializes `data_path`); `delete_project` removes app-managed folders (those under `default_content_root`) but preserves user-chosen external folders; committed `priv/data/projects/default/` content removed from the repo and `/priv/data/projects/` git-ignored; test config redirects `:default_content_root` to a temp dir; 4 tests added (default folder outside repo/private, no-repo fallback, registry round-trip, registry cleanup on delete). | -| A1-17 | `bds://new-post` blogmark deep link is never received or routed | script.allium:74 (`BlogmarkReceived`), script.allium:233-267 (`ExecuteTransform`/`TransformTrigger`), editor_settings.allium:141-143 (`BookmarkletCopy`) | Only the bookmarklet JS string is generated (`app_shell.ex:48`); nothing handles the resulting `bds://new-post?title=&url=` deep link. The `ExecuteTransform` engine now exists (`BDS.Scripts.Transforms.run/3`, A1-9) but no code emits `BlogmarkReceived(data)`, so the pipeline is never triggered and no post candidate is created. | **Open:** register a `bds://` URL-scheme handler in the desktop layer, parse `new-post` query params into a candidate `{title, content?, tags, categories, url}`, run `BDS.Scripts.Transforms.run/3`, then create a draft post (defaulting category from `blogmark_category`) and open it in the editor; surface accepted transform toasts. | +| A1-17 | `bds2://new-post` blogmark deep link is never received or routed | script.allium:74 (`BlogmarkReceived`), script.allium:233-268 (`ExecuteTransform`/`TransformTrigger`), editor_settings.allium:141-143 (`BookmarkletCopy`) | Only the bookmarklet JS string is generated (`app_shell.ex:48`, now emitting the `bds2://` scheme so it does not clash with the legacy app's `bds://`); nothing handles the resulting `bds2://new-post?title=&url=` deep link. The `ExecuteTransform` engine now exists (`BDS.Scripts.Transforms.run/3`, A1-9) but no code emits `BlogmarkReceived(data)`, so the pipeline is never triggered and no post candidate is created. | **Open:** register a `bds2://` URL-scheme handler in the desktop layer, parse `new-post` query params into a candidate `{title, content?, tags, categories, url}`, run `BDS.Scripts.Transforms.run/3`, then create a draft post (defaulting category from `blogmark_category`) and open it in the editor; surface accepted transform toasts. | ### A2. Spec Should Update (code is normative) @@ -190,7 +190,7 @@ All reconciled to follow code. Specs must be self-consistent and match code. 1. ~~**A1-1 through A1-15**~~ — all resolved: auto-save, on-demand preview, template lookup, validation gates, real Pagefind, graceful shutdown, real embedding model, HNSW ANN index, Apple GPU/EMLX acceleration (A1-14c), and preview/generation content strategy (A1-15) 1b. ~~**A1-16**~~ — storage-location compliance resolved: public content now lives under a per-user default content location (never the repo/private dir), `priv/data/projects/` fallback dropped, machine-local project registry added, committed default project content removed from repo -1c. **A1-17** — blogmark deep-link OS handler not implemented: the `ExecuteTransform` engine exists but nothing receives `bds://new-post` and emits `BlogmarkReceived(data)` to trigger it +1c. **A1-17** — blogmark deep-link OS handler not implemented: the `ExecuteTransform` engine exists but nothing receives `bds2://new-post` and emits `BlogmarkReceived(data)` to trigger it 2. **D1-1 through D1-18** — untested invariants/guarantees 3. **C-1 through C-3** — internal spec inconsistencies (reconcile to code) 4. **B1-1 through B1-6** — major code behaviors missing from spec diff --git a/lib/bds/scripting/capabilities/app_shell.ex b/lib/bds/scripting/capabilities/app_shell.ex index 6ee29a9..ac09b20 100644 --- a/lib/bds/scripting/capabilities/app_shell.ex +++ b/lib/bds/scripting/capabilities/app_shell.ex @@ -46,7 +46,7 @@ defmodule BDS.Scripting.Capabilities.AppShell do end def blogmark_bookmarklet do - "javascript:(()=>{const t=encodeURIComponent(document.title||'');const u=encodeURIComponent(location.href||'');location.href='bds://new-post?title='+t+'&url='+u;})();" + "javascript:(()=>{const t=encodeURIComponent(document.title||'');const u=encodeURIComponent(location.href||'');location.href='bds2://new-post?title='+t+'&url='+u;})();" end def title_bar_metrics(opts) do diff --git a/lib/bds/scripts/transforms.ex b/lib/bds/scripts/transforms.ex index 3a3742c..7a914c5 100644 --- a/lib/bds/scripts/transforms.ex +++ b/lib/bds/scripts/transforms.ex @@ -3,7 +3,7 @@ defmodule BDS.Scripts.Transforms do Runs the blogmark transform pipeline (spec: script.allium `ExecuteTransform`). Enabled `transform` scripts for a project are applied sequentially to a post - candidate produced by a `bds://new-post` blogmark deep link. Each transform + candidate produced by a `bds2://new-post` blogmark deep link. Each transform receives the current candidate plus a context describing the blogmark source and origin URL, and returns the modified candidate. diff --git a/specs/script.allium b/specs/script.allium index 8009a40..3b2a8bf 100644 --- a/specs/script.allium +++ b/specs/script.allium @@ -262,7 +262,8 @@ rule ExecuteTransform { -- config.transform_max_toast_length characters. @guidance - -- bds://new-post deep links from browser bookmarks + -- bds2://new-post deep links from browser bookmarks + -- (bds2:// scheme avoids clashing with the legacy app's bds:// scheme) -- Ordering is deterministic: updated_at, then slug, then id } diff --git a/test/bds/scripting/api_test.exs b/test/bds/scripting/api_test.exs index 008bf6f..6684beb 100644 --- a/test/bds/scripting/api_test.exs +++ b/test/bds/scripting/api_test.exs @@ -645,6 +645,13 @@ defmodule BDS.Scripting.ApiTest do assert result["startup_project_name"] == "Scripting API" end + test "blogmark bookmarklet uses the bds2:// scheme to avoid clashing with the old app" do + bookmarklet = BDS.Scripting.Capabilities.AppShell.blogmark_bookmarklet() + + assert String.contains?(bookmarklet, "bds2://new-post?") + refute String.contains?(bookmarklet, "'bds://new-post") + end + defp write_binary_fixture(base_dir, name, contents) do path = Path.join(base_dir, name) File.write!(path, contents)