Шеринг по URL: Совместная работа над планами без бэкенда
Чему вы научитесь
- ✅ Делиться планами и аннотациями через URL без входа в аккаунт или развёртывания сервера
- ✅ Понимать, как deflate-сжатие и Base64-кодирование встраивают данные в URL hash
- ✅ Различать режим шеринга (только чтение) и локальный режим (редактирование)
- ✅ Настраивать переменную окружения
PLANNOTATOR_SHAREдля управления функцией шеринга - ✅ Обрабатывать ограничения длины URL и ситуации с неудачным шерингом
Ваши текущие проблемы
Проблема 1: Хотите попросить коллег проверить план, сгенерированный ИИ, но нет платформы для совместной работы.
Проблема 2: Делитесь результатами ревью через скриншоты или копирование текста, но получатель не видит ваши аннотации напрямую.
Проблема 3: Развёртывание сервера для онлайн-совместной работы дорого, или политика безопасности компании это запрещает.
Проблема 4: Нужен простой и быстрый способ поделиться, но непонятно, как обеспечить конфиденциальность данных.
Plannotator поможет вам:
- Не нужен бэкенд-сервер, все данные сжаты в URL
- Ссылка для шеринга содержит полный план и аннотации, получатель может их просмотреть
- Данные не покидают локальное устройство, конфиденциальность защищена
- Сгенерированный URL можно скопировать в любой мессенджер
Когда использовать этот метод
Подходящие сценарии:
- Нужно, чтобы коллеги проверили план реализации, сгенерированный ИИ
- Хотите поделиться результатами код-ревью с коллегами
- Нужно сохранить результаты ревью в заметки (интеграция с Obsidian/Bear)
- Быстро получить обратную связь по плану
Неподходящие сценарии:
- Нужно совместное редактирование в реальном времени (шеринг в Plannotator только для чтения)
- Содержимое плана превышает лимит длины URL (обычно несколько тысяч строк)
- Содержимое для шеринга включает конфиденциальную информацию (сам URL не зашифрован)
Предупреждение о безопасности
URL для шеринга содержит полный план и аннотации. Не делитесь контентом с конфиденциальной информацией (API-ключи, пароли и т.д.). URL для шеринга доступен любому и не имеет срока действия.
Основная концепция
Что такое шеринг по URL
Шеринг по URL — это способ совместной работы без бэкенда, предоставляемый Plannotator. Он сжимает план и аннотации в URL hash, реализуя функцию шеринга без сервера.
Почему «без бэкенда»?
Традиционные решения для совместной работы требуют бэкенд-сервер для хранения планов и аннотаций, пользователи получают доступ по ID или токену. Шеринг по URL в Plannotator не зависит от бэкенда — все данные находятся в URL, получатель открывает ссылку и парсит содержимое. Это гарантирует конфиденциальность (данные не загружаются) и простоту (не нужно развёртывать сервис).
Принцип работы
┌─────────────────────────────────────────────────────────┐
│ Пользователь A (отправитель) │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. Ревью плана, добавление аннотаций │
│ ┌──────────────────────┐ │
│ │ Plan: План реализации │ │
│ │ Annotations: [ │ │
│ │ {type: 'REPLACE'},│ │
│ │ {type: 'COMMENT'} │ │
│ │ ] │ │
│ └──────────────────────┘ │
│ │ │
│ ▼ │
│ 2. Клик Export → Share │
│ │ │
│ ▼ │
│ 3. Сжатие данных │
│ JSON → deflate → Base64 → URL-безопасные символы │
│ ↓ │
│ https://share.plannotator.ai/#eJyrVkrLz1... │
│ │
└─────────────────────────────────────────────────────────┘
│
│ Копирование URL
▼
┌─────────────────────────────────────────────────────────┐
│ Пользователь B (получатель) │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. Открытие URL для шеринга │
│ https://share.plannotator.ai/#eJyrVkrLz1... │
│ │ │
│ ▼ │
│ 2. Браузер парсит hash │
│ URL-безопасные символы → Base64 декод → deflate распаковка → JSON │
│ │ │
│ ▼ │
│ 3. Восстановление плана и аннотаций │
│ ┌──────────────────────┐ │
│ │ Plan: План реализации │ ✅ Режим только чтение │
│ │ Annotations: [ │ (нельзя отправить решение)│
│ │ {type: 'REPLACE'},│ │
│ │ {type: 'COMMENT'} │ │
│ │ ] │ │
│ └──────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘Детали алгоритма сжатия
Шаг 1: JSON-сериализация
{
"p": "# Plan\n\nStep 1...",
"a": [
["R", "old text", "new text", null, null],
["C", "context", "comment text", null, null]
],
"g": ["image1.png", "image2.png"]
}Шаг 2: Deflate-raw сжатие
- Используется нативный API
CompressionStream('deflate-raw') - Типичная степень сжатия 60-80% (зависит от повторяемости текста, не определено в исходном коде)
- Расположение в коде:
packages/ui/utils/sharing.ts:34
Шаг 3: Base64-кодирование
const base64 = btoa(String.fromCharCode(...compressed));Шаг 4: Замена на URL-безопасные символы
base64
.replace(/\+/g, '-') // + → -
.replace(/\//g, '_') // / → _
.replace(/=/g, ''); // = → '' (удаление паддинга)Зачем заменять специальные символы?
Некоторые символы в URL имеют специальное значение (например, + означает пробел, / — разделитель пути). После Base64-кодирования могут появиться эти символы, что приведёт к ошибкам парсинга URL. После замены на - и _ URL становится безопасным и копируемым.
Оптимизация формата аннотаций
Для эффективности сжатия Plannotator использует компактный формат аннотаций (ShareableAnnotation):
| Исходная Annotation | Компактный формат | Описание |
|---|---|---|
{type: 'DELETION', originalText: '...', text: undefined, ...} | ['D', 'old text', null, images?] | D = Deletion, null означает отсутствие text |
{type: 'REPLACEMENT', originalText: '...', text: 'new...', ...} | ['R', 'old text', 'new text', null, images?] | R = Replacement |
{type: 'COMMENT', originalText: '...', text: 'comment...', ...} | ['C', 'old text', 'comment text', null, images?] | C = Comment |
{type: 'INSERTION', originalText: '...', text: 'new...', ...} | ['I', 'context', 'new text', null, images?] | I = Insertion |
{type: 'GLOBAL_COMMENT', text: '...', ...} | ['G', 'comment text', null, images?] | G = Global comment |
Порядок полей фиксирован, имена ключей опущены, что значительно уменьшает объём данных. Расположение в коде: packages/ui/utils/sharing.ts:76
Структура URL для шеринга
https://share.plannotator.ai/#<compressed_data>
↑
часть hash- Базовый домен:
share.plannotator.ai(отдельная страница для шеринга) - Разделитель Hash:
#(не отправляется на сервер, полностью парсится фронтендом) - Сжатые данные: Base64url-закодированный сжатый JSON
🎒 Подготовка перед началом
Предварительные требования:
- ✅ Пройден Основы ревью планов, понимание добавления аннотаций
- ✅ Пройден Туториал по аннотациям планов, понимание типов аннотаций
- ✅ Браузер поддерживает API
CompressionStream(все современные браузеры поддерживают)
Проверка включения функции шеринга:
# По умолчанию включено
echo $PLANNOTATOR_SHARE
# Для отключения (например, по политике безопасности компании)
export PLANNOTATOR_SHARE=disabledОписание переменной окружения
PLANNOTATOR_SHARE управляет состоянием функции шеринга:
- Не установлена или не "disabled": функция шеринга включена
- Установлена в "disabled": шеринг отключён (в Export Modal отображается только вкладка Raw Diff)
Расположение в коде: apps/hook/server/index.ts:44, apps/opencode-plugin/index.ts:50
Проверка совместимости браузера:
# Выполните в консоли браузера
const stream = new CompressionStream('deflate-raw');
console.log('CompressionStream supported');Если выводится CompressionStream supported, браузер поддерживает эту функцию. Современные браузеры (Chrome 80+, Firefox 113+, Safari 16.4+) поддерживают.
Пошаговое руководство
Шаг 1: Завершите ревью плана
Зачем Перед шерингом нужно завершить ревью, включая добавление аннотаций.
Действия:
- Запустите ревью плана в Claude Code или OpenCode
- Просмотрите содержимое плана, выделите текст для изменения
- Добавьте аннотации (удаление, замена, комментарий и т.д.)
- (Опционально) Загрузите изображения-вложения
Вы должны увидеть:
┌─────────────────────────────────────────────────────────────┐
│ Plan Review │
├─────────────────────────────────────────────────────────────┤
│ │
│ # Implementation Plan │
│ │
│ ## Phase 1: Setup │
│ Set up WebSocket server on port 8080 │
│ │
│ ## Phase 2: Authentication │
│ Implement JWT authentication middleware │
│ ┌─────────────────────┐ │
│ ━━━━━━━━━━━━━━━━│ Replace: "implement" │ │
│ └─────────────────────┘ │
│ │
│ Annotation Panel │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ REPLACE: "implement" → "add" │ │
│ │ JWT is overkill, use simple session tokens │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ [Approve] [Request Changes] [Export] │
└─────────────────────────────────────────────────────────────┘Шаг 2: Откройте Export Modal
Зачем Export Modal предоставляет точку входа для генерации URL шеринга.
Действия:
- Нажмите кнопку Export в правом верхнем углу
- Дождитесь открытия Export Modal
Вы должны увидеть:
┌─────────────────────────────────────────────────────────────┐
│ Export × │
│ 1 annotation Share | Raw Diff │
├─────────────────────────────────────────────────────────────┤
│ │
│ Shareable URL │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ https://share.plannotator.ai/#eJyrVkrLz1... │ │
│ │ [Copy] │ │
│ │ 3.2 KB │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ This URL contains full plan and all annotations. │
│ Anyone with this link can view and add to your annotations.│
│ │
└─────────────────────────────────────────────────────────────┘Подсказка о размере URL
В правом нижнем углу отображается размер URL в байтах (например, 3.2 KB). Если URL слишком длинный (более 8 KB), рассмотрите уменьшение количества аннотаций или вложений-изображений.
Шаг 3: Скопируйте URL для шеринга
Зачем После копирования URL можно вставить в любой мессенджер (Slack, Email, WeChat и т.д.).
Действия:
- Нажмите кнопку Copy
- Дождитесь изменения кнопки на Copied!
- URL скопирован в буфер обмена
Вы должны увидеть:
┌─────────────────────────────────────────────────────────────┐
│ Shareable URL │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ https://share.plannotator.ai/#eJyrVkrLz1... │ │
│ │ ✓ Copied │ │
│ │ 3.2 KB │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘Автовыделение
Клик по полю ввода URL автоматически выделяет всё содержимое, что удобно для ручного копирования (если не используете кнопку Copy).
Шаг 4: Отправьте URL коллегам
Зачем Коллеги смогут просмотреть план и аннотации, открыв URL.
Действия:
- Вставьте URL в мессенджер (Slack, Email и т.д.)
- Отправьте членам команды
Пример сообщения:
Привет, @команда,
Пожалуйста, проверьте этот план реализации:
https://share.plannotator.ai/#eJyrVkrLz1...
Я добавил аннотацию замены на фазе 2, считаю что JWT слишком сложен.
Жду вашей обратной связи, спасибо!Шаг 5: Коллега открывает URL для шеринга (получатель)
Зачем Коллеге нужно открыть URL в браузере для просмотра содержимого.
Действия (выполняет коллега):
- Кликните по URL для шеринга
- Дождитесь загрузки страницы
Вы должны увидеть (с точки зрения коллеги):
┌─────────────────────────────────────────────────────────────┐
│ Plan Review Read-only │
├─────────────────────────────────────────────────────────────┤
│ │
│ # Implementation Plan │
│ │
│ ## Phase 1: Setup │
│ Set up WebSocket server on port 8080 │
│ │
│ ## Phase 2: Authentication │
│ Implement JWT authentication middleware │
│ ┌─────────────────────┐ │
│ ━━━━━━━━━━━━━━━━│ Replace: "implement" │ │
│ │ └─────────────────────┘ │
│ │ This annotation was shared by [Your Name] │
│ │
│ Annotation Panel │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ REPLACE: "implement" → "add" │ │
│ │ JWT is overkill, use simple session tokens │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ [View Only Mode - Approve and Deny disabled] │
└─────────────────────────────────────────────────────────────┘Режим только для чтения
После открытия URL для шеринга в правом верхнем углу интерфейса отображается метка "Read-only", кнопки Approve и Deny отключены. Коллега может просматривать план и аннотации, но не может отправить решение.
Процесс распаковки
Когда коллега открывает URL, браузер автоматически выполняет следующие шаги (запускается хуком useSharing):
- Извлечение сжатых данных из
window.location.hash - Обратное выполнение: Base64 декодирование → deflate распаковка → JSON парсинг
- Восстановление плана и аннотаций
- Очистка URL hash (предотвращение повторной загрузки при обновлении)
Расположение в коде: packages/ui/hooks/useSharing.ts:67
Контрольная точка ✅
Проверка валидности URL для шеринга:
- Скопируйте URL для шеринга
- Откройте в новой вкладке или режиме инкогнито
- Убедитесь, что отображаются тот же план и аннотации
Проверка режима только для чтения:
- Коллега открывает URL для шеринга
- Проверьте наличие метки "Read-only" в правом верхнем углу
- Убедитесь, что кнопки Approve и Deny отключены
Проверка длины URL:
- Посмотрите размер URL в Export Modal
- Убедитесь, что не превышает 8 KB (если превышает, уменьшите количество аннотаций)
Типичные проблемы
Проблема 1: Кнопка шеринга URL не отображается
Симптом: В Export Modal нет вкладки Share, только Raw Diff.
Причина: Переменная окружения PLANNOTATOR_SHARE установлена в "disabled".
Решение:
# Проверить текущее значение
echo $PLANNOTATOR_SHARE
# Удалить или установить другое значение
unset PLANNOTATOR_SHARE
# или
export PLANNOTATOR_SHARE=enabledРасположение в коде: apps/hook/server/index.ts:44
Проблема 2: URL для шеринга открывается с пустой страницей
Симптом: Коллега открывает URL, страница пустая.
Причина: URL hash потерян или обрезан при копировании.
Решение:
- Убедитесь, что скопирован полный URL (включая
#и все символы после него) - Не используйте сервисы сокращения ссылок (могут обрезать hash)
- Используйте кнопку Copy в Export Modal вместо ручного копирования
Длина URL hash
Часть hash в URL для шеринга обычно содержит несколько тысяч символов, при ручном копировании легко пропустить часть. Рекомендуется использовать кнопку Copy или дважды проверить полноту после копирования-вставки.
Проблема 3: URL слишком длинный, невозможно отправить
Симптом: URL превышает лимит символов мессенджера (WeChat, Slack и т.д.).
Причина: Содержимое плана слишком длинное или слишком много аннотаций.
Решение:
- Удалите ненужные аннотации
- Уменьшите количество вложений-изображений
- Рассмотрите экспорт Raw Diff и сохранение в файл
- Используйте функцию код-ревью (режим diff имеет более высокую степень сжатия)
Проблема 4: Коллега не видит мои изображения
Симптом: URL для шеринга содержит пути к изображениям, но у коллеги отображается "Image not found".
Причина: Изображения сохранены в локальной директории /tmp/plannotator/, коллега не имеет к ней доступа.
Решение:
- Шеринг URL в Plannotator не поддерживает доступ к изображениям между устройствами
- Рекомендуется использовать интеграцию с Obsidian, после сохранения изображений в vault можно делиться
- Или сделайте скриншот и добавьте текстовое описание в аннотацию
Расположение в коде: packages/server/index.ts:163 (путь сохранения изображений)
Проблема 5: После изменения аннотаций URL не обновился
Симптом: После добавления новых аннотаций URL в Export Modal не изменился.
Причина: Состояние shareUrl не обновилось автоматически (редкий случай, обычно проблема обновления состояния React).
Решение:
- Закройте Export Modal
- Откройте Export Modal заново
- URL должен автоматически обновиться до актуального содержимого
Расположение в коде: packages/ui/hooks/useSharing.ts:128 (функция refreshShareUrl)
Итоги урока
Функция шеринга по URL позволяет делиться планами и аннотациями без бэкенд-сервера:
- ✅ Без бэкенда: данные сжаты в URL hash, не зависит от сервера
- ✅ Конфиденциальность: данные не загружаются, передаются только между вами и коллегой
- ✅ Простота и эффективность: генерация URL одним кликом, копирование и вставка для шеринга
- ✅ Режим только для чтения: коллега может просматривать и добавлять аннотации, но не может отправить решение
Технические принципы:
- Deflate-raw сжатие: сжимает JSON данные примерно на 60-80%
- Base64 кодирование: преобразует бинарные данные в текст
- Замена на URL-безопасные символы:
+→-,/→_,=→'' - Парсинг Hash: фронтенд автоматически распаковывает и восстанавливает содержимое
Параметры конфигурации:
PLANNOTATOR_SHARE=disabled: отключить функцию шеринга- По умолчанию включено: функция шеринга доступна
Анонс следующего урока
В следующем уроке мы изучим Интеграцию с Obsidian.
Вы узнаете:
- Автоматическое обнаружение Obsidian vaults
- Сохранение одобренных планов в Obsidian
- Автоматическая генерация frontmatter и тегов
- Сочетание шеринга по URL и управления знаниями в Obsidian
Анонс следующего урока
В следующем уроке мы изучим Интеграцию с Obsidian.
Вы узнаете:
- Как настроить интеграцию с Obsidian для автоматического сохранения планов в vault
- Понимание механизма генерации frontmatter и тегов
- Использование backlink для построения графа знаний
Приложение: Справочник по исходному коду
Нажмите для просмотра расположения в исходном коде
Дата обновления: 2026-01-24
| Функция | Путь к файлу | Строки |
|---|---|---|
| Сжатие данных (deflate + Base64) | packages/ui/utils/sharing.ts | 30-48 |
| Распаковка данных | packages/ui/utils/sharing.ts | 53-71 |
| Преобразование формата аннотаций (компактный) | packages/ui/utils/sharing.ts | 76-95 |
| Восстановление формата аннотаций | packages/ui/utils/sharing.ts | 102-155 |
| Генерация URL для шеринга | packages/ui/utils/sharing.ts | 162-175 |
| Парсинг URL hash | packages/ui/utils/sharing.ts | 181-194 |
| Форматирование размера URL | packages/ui/utils/sharing.ts | 199-205 |
| Хук шеринга URL | packages/ui/hooks/useSharing.ts | 45-155 |
| UI Export Modal | packages/ui/components/ExportModal.tsx | 1-196 |
| Конфигурация переключателя шеринга (Hook) | apps/hook/server/index.ts | 44 |
| Конфигурация переключателя шеринга (OpenCode) | apps/opencode-plugin/index.ts | 50 |
Ключевые константы:
SHARE_BASE_URL = 'https://share.plannotator.ai': базовый домен страницы шеринга
Ключевые функции:
compress(payload: SharePayload): Promise<string>: сжимает payload в строку base64urldecompress(b64: string): Promise<SharePayload>: распаковывает строку base64url в payloadtoShareable(annotations: Annotation[]): ShareableAnnotation[]: преобразует полные аннотации в компактный форматfromShareable(data: ShareableAnnotation[]): Annotation[]: восстанавливает компактный формат в полные аннотацииgenerateShareUrl(markdown, annotations, attachments): Promise<string>: генерирует полный URL для шерингаparseShareHash(): Promise<SharePayload | null>: парсит hash текущего URL
Типы данных:
interface SharePayload {
p: string; // plan markdown
a: ShareableAnnotation[];
g?: string[]; // global attachments
}
type ShareableAnnotation =
| ['D', string, string | null, string[]?] // Deletion
| ['R', string, string, string | null, string[]?] // Replacement
| ['C', string, string, string | null, string[]?] // Comment
| ['I', string, string, string | null, string[]?] // Insertion
| ['G', string, string | null, string[]?]; // Global Comment