| 1 | # ADR 0157: Magic Link `cide://` — deep link из браузера и доков в Cascade IDE |
| 2 | |
| 3 | **Статус:** Accepted |
| 4 | **Дата:** 2026-05-28 |
| 5 | |
| 6 | ## Резюме |
| 7 | |
| 8 | - Кастомная схема **`cide://`** (Magic Link) позволяет с публичного сайта документации, ADR на GitHub Pages или внутреннего портала **открыть Cascade IDE**, загрузить workspace и **перейти к коду или markdown**. |
| 9 | - v1 переиспользует **CodeAnchor / bracket** ([0128](0128-intercom-attachment-anchors-and-code-references.md), [0156](0156-correspondence-mfd-surface-and-reverse-code-anchors.md)) и существующие пути **`IdeMcp.GoToPosition`** / **Markdown Preview**. |
| 10 | - Регистрация протокола в ОС — **opt-in** (скрипт `tools/Register-CideMagicLinkProtocol.ps1` для dev); в installer — отдельный этап. |
| 11 | |
| 12 | ## Связанные ADR |
| 13 | |
| 14 | | ADR | Роль | |
| 15 | |-----|------| |
| 16 | | [0156](0156-correspondence-mfd-surface-and-reverse-code-anchors.md) | Bracket / CodeAnchor, CRS | |
| 17 | | [0155](0155-documentation-code-correspondence-and-architectural-drift.md) | Док ↔ код | |
| 18 | | [0128](0128-intercom-attachment-anchors-and-code-references.md) | `AttachmentAnchor`, bracket parse | |
| 19 | | [0061](0061-context-aware-adr-map-pfd-knowledge-indicator.md) | ADR map, preview opener | |
| 20 | |
| 21 | --- |
| 22 | |
| 23 | ## Контекст |
| 24 | |
| 25 | Preview в IDE уже умеет `cascade-code-anchor:` и клик → редактор. Документация на **внешнем сайте** не может вызвать IDE без **зарегистрированного URL handler**. Аналоги: `vscode://`, `cursor://`, `jetbrains://idea/navigate/...`. |
| 26 | |
| 27 | Цель: ссылка в prose ADR на сайте вида «открыть в Cascade IDE» без копирования пути. |
| 28 | |
| 29 | --- |
| 30 | |
| 31 | ## Решение |
| 32 | |
| 33 | ### 1. Схема и команды (v1) |
| 34 | |
| 35 | | Команда (`host`) | Назначение | |
| 36 | |------------------|------------| |
| 37 | | **`reveal`** | Открыть файл в редакторе, опционально строка / bracket | |
| 38 | | **`open`** | Загрузить solution/workspace, затем опционально `reveal` | |
| 39 | | **`md`** | Открыть markdown в MFD Markdown Preview, опционально scroll | |
| 40 | |
| 41 | Общие query-параметры: |
| 42 | |
| 43 | | Параметр | Обязателен | Смысл | |
| 44 | |----------|------------|--------| |
| 45 | | `root` | для `open` / когда workspace ещё не загружен | Абсолютный путь к **корню workspace** (каталог с `.cascade/workspace.toml` или `.sln`) | |
| 46 | | `f` | для `reveal` (если нет `b`) | Repo-relative путь к файлу | |
| 47 | | `l`, `le` | нет | 1-based строки | |
| 48 | | `b` | альтернатива `f`+`l` | Bracket inner (URL-encoded): `F:…; M:…` или H1 | |
| 49 | | `sln` | для `open` | Относительный или абсолютный путь к `.sln` / `.slnx` | |
| 50 | | `doc` | для `md` | Repo-relative путь к `.md` | |
| 51 | | `c` | для `md` | 1-based column (зарезервировано) | |
| 52 | |
| 53 | Примеры: |
| 54 | |
| 55 | ```text |
| 56 | cide://reveal?root=D:%2Frepo%2Fcascade-ide&f=Features%2FWorkspaceNavigation%2FApplication%2FDocReverseAnchorResolver.cs&l=84 |
| 57 | |
| 58 | cide://reveal?root=D:%2Frepo%2Fcascade-ide&b=F%3AFeatures%2FWorkspaceNavigation%2FApplication%2FDocReverseAnchorResolver.cs%3B%20M%3AResolve |
| 59 | |
| 60 | cide://open?root=D:%2Frepo%2Fcascade-ide&sln=CascadeIDE.sln |
| 61 | |
| 62 | cide://md?root=D:%2Frepo%2Fcascade-ide&doc=docs%2Fadr%2F0156-correspondence-mfd-surface-and-reverse-code-anchors.md&l=120 |
| 63 | ``` |
| 64 | |
| 65 | На HTML-сайте рядом — fallback: обычная ссылка на GitHub + подпись «требуется Cascade IDE». |
| 66 | |
| 67 | ### 2. Безопасность (threat model v1) |
| 68 | |
| 69 | | Угроза | Митигация | |
| 70 | |--------|-----------| |
| 71 | | Произвольное чтение файлов | `root` должен пройти **workspace validation**: каталог существует и содержит `.cascade/workspace.toml` **или** discoverable `.sln` | |
| 72 | | Path traversal в `f` / `doc` | Нормализация; итоговый путь должен оставаться **под** `root` | |
| 73 | | Запуск произвольных exe | Handler только на `CascadeIDE.exe`, аргумент — один URI | |
| 74 | | Фишинг с поддельным `root` | Пользователь подтверждает открытие IDE; v1 без silent cross-drive | |
| 75 | |
| 76 | Не поддерживается в v1: `file://`, выполнение shell, произвольные MCP-команды через URI. |
| 77 | |
| 78 | **Публичный сайт (пока не делаем):** ссылка с **абсолютным** `root=D:\…` или `%USERPROFILE%\…` в HTML — утечка layout машины автора и почти всегда **битая** у читателя. Это не RCE, но фишинг (`root` на чужой клон) и путаница. Для сайта — только после **M3** (`workspace.id` → resolve на клиенте) или кнопка «скопировать bracket» без URI. До тех пор: GitHub fallback + opt-in dev script. |
| 79 | |
| 80 | ### 3. Single instance |
| 81 | |
| 82 | | Сценарий | Поведение | |
| 83 | |----------|-----------| |
| 84 | | IDE не запущен, URI в argv | Старт IDE → после `MainWindow` Loaded — выполнить link | |
| 85 | | IDE запущен, второй процесс с URI | **Named pipe** `CascadeIDE.MagicLink.v1` → передать URI первому процессу → второй **exit 0** | |
| 86 | | IDE запущен, второй процесс без URI | Обычный второй экземпляр (не блокируем) | |
| 87 | |
| 88 | Реализация: `CideMagicLinkSingleInstance` + `CideMagicLinkPipeHost`. |
| 89 | |
| 90 | ### 4. Код (v1) |
| 91 | |
| 92 | | Компонент | Путь | |
| 93 | |-----------|------| |
| 94 | | Parse | [F:Features/MagicLink/CideMagicLinkUri.cs] | |
| 95 | | Execute | [F:Features/MagicLink/CideMagicLinkExecutor.cs] | |
| 96 | | Single instance | [F:Features/MagicLink/CideMagicLinkSingleInstance.cs] | |
| 97 | | VM hook | [F:ViewModels/MainWindowViewModel.MagicLink.cs] | |
| 98 | | Startup argv | [F:Program.cs] | |
| 99 | | Dev registry | [F:tools/Register-CideMagicLinkProtocol.ps1] | |
| 100 | |
| 101 | Цепочка `reveal` + `b`: `BracketCodeReferenceParser.TryParse` → `TryToAttachmentAnchor` → `IdeMcp.GoToPosition` (как preview code-anchor). |
| 102 | |
| 103 | ### 5. Генерация ссылок для сайта (не IDE) |
| 104 | |
| 105 | Рекомендация для CI/docs build (будущее): |
| 106 | |
| 107 | - Шаблон из `root` = canonical clone path или placeholder `%CIDE_WORKSPACE_ROOT%` с подстановкой на машине разработчика. |
| 108 | - Bracket в ADR → `cide://reveal?root=…&b=…` через тот же expander, что preview (`MarkdownCodeAnchorPreviewExpander` — только схема `cide`). |
| 109 | |
| 110 | --- |
| 111 | |
| 112 | ## Альтернативы (отклонены) |
| 113 | |
| 114 | | Альтернатива | Почему нет | |
| 115 | |--------------|------------| |
| 116 | | Только `vscode://` | Не открывает CRS / correspondence / Cascade MCP | |
| 117 | | Custom protocol без `root` | Нельзя безопасно infer workspace на чужой машине | |
| 118 | | HTTP localhost redirect | Firewall, второй сервер; хуже UX чем OS handler | |
| 119 | |
| 120 | --- |
| 121 | |
| 122 | ## Дорожная карта |
| 123 | |
| 124 | | Этап | Работа | Критерий | |
| 125 | |------|--------|----------| |
| 126 | | **M1** | ADR 0157 + parse + `reveal`/`open`/`md` | Клик из `Register-CideMagicLinkProtocol.ps1` открывает файл | |
| 127 | | **M2** | Single-instance pipe | Второй клик не плодит окна | |
| 128 | | **M3** | Docs site generator + `workspace.id` в TOML | Стабильный `root` без абсолютного пути | |
| 129 | | **M4** | Installer protocol registration | Production setup | |
| 130 | |
| 131 | --- |
| 132 | |
| 133 | ## Статус реализации |
| 134 | |
| 135 | | Компонент | Статус | |
| 136 | |-----------|--------| |
| 137 | | ADR 0157 | **Accepted** | |
| 138 | | Parse + security | **Implemented** (M1) | |
| 139 | | `reveal` / `open` / `md` | **Implemented** (M1) | |
| 140 | | Single-instance pipe | **Implemented** (M2) | |
| 141 | | Dev protocol script | **Implemented** (M1) | |
| 142 | | Docs site generator | **Deferred (M3)** — публичный сайт без `workspace.id`; не класть абсолютный `root` в HTML | |
| 143 | |