Skip to content

dsh-sidebar-pins

Verified

dsh-sidebar-pins · v0.1.0 · MIT · Web UI

Pinned-sessions block for the DeepSeek Harness sidebar: a collapsible pinned panel above the Settings row, hover pin buttons on session rows and a session-header toggle. Pins live in the plugin's own settings namespace and are migrated once from dsh-sessi

Install

dsh plugin add dsh-sidebar-pins

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Tags

Creators

Readme

English | Русский

dsh-sidebar-pins

Самодостаточные закреплённые сессии для левой панели DeepSeek Harness: свой набор пинов, свои кнопки закрепления и блок «📌 ЗАКРЕПЛЁННЫЕ» сверху сайдбара — вместо всплывающего окна, которое уезжает в правый верхний угол и закрывается от клика мимо.

Плагин написан под DSH 0.1.5-rc.1 и заменяет dsh-session-pin целиком: после его установки старый плагин можно удалить (см. «Удаление старого плагина»).

Зачем

dsh-session-pin 0.7.11 на этой версии ядра не может выдать то, что обещает:

  • он переставляет хостовый список (workspaces.insertSessionBefore) — и тот действительно переставлен, это видно в ~/.dsh/storages/workspace.json;
  • а сайдбар рисует браузерный порядок (dsh.workspace.view.v5), поверх которого хостовый список уже не влияет, — публичного API к этому хранилищу у плагинов нет;
  • слот sidebar.workspaces объявлен ядром как kind: "single", поэтому встроить в него второй блок невозможно;
  • слота sessions.row.action на 0.1.5-rc.1 нет, поэтому кнопки в строках — DOM-оверлей.

Что делает

Поверхность Поведение
Блок «Закреплённые» Внутри подвала сайдбара, над строкой «Настройки». Место выводится только из собственной метки-якоря: собирается вся цепочка контейнеров от неё вверх, и панель садится перед той веткой, что ведёт к якорю. Контейнеры, которые раскладывают детей в строку (штатный footerActions — именно такой), пропускаются по измерению, поэтому панель не встаёт сбоку от «Настройки»; уровни выше корня сайдбара не рассматриваются вообще — иначе панель оказалась бы над всем сайдбаром. Высота — до 30vh, список пинов прокручивается внутри. Сворачивание вручную помнится per-browser; в режиме рейки панель показывает только иконку и количество
Строка пина Клик — открыть сессию; × справа — открепить (появляется при наведении)
Кнопка в строке сессии 📌 при наведении на строку — закрепить/открепить. Сессия определяется по React-fiber, с откатом на вложенный span с уникальным заголовком
Кнопка в шапке сессии Тумблер закрепа открытой сессии (слот conversation.session.header.actions)
Пропавшие сессии Остаются в списке приглушёнными (сырой id), чтобы их можно было открепить
Легаси-панель Плавающая панель, подвал «Pinned sessions», полоса бордов, старая кнопка в шапке и старые кнопки в строках скрываются — вся закреп-UI теперь здесь

Плагин ничего не пишет в лог сессии, не обращается к сети и не влияет на запросы к модели.

Хранилище

  • Основное место — свой namespace настроек sidebar-pins (регистрирует хостовая половина), то есть пины durable и видны в ~/.dsh/settings.yaml.
  • Если клиент не видит namespace (хост-половина не смонтировалась, remote-браузер) — откат на localStorage["dsh.sidebar-pins.pinned"].
  • При первом запуске набор разово переносится из session-pin (хостовой половиной, до открытия страницы), поэтому пины переживают удаление старого плагина. Перенос идёт только пока поле pinned не появилось в пользовательском слое: осознанно опустошённый набор больше не заполняется.

Установка

Из GitHub:

dsh plugin --profile web add github:AndreyTepaykin/dsh-sidebar-pins

Из npm (когда пакет опубликован):

dsh plugin --profile web add dsh-sidebar-pins

Для локальной разработки — из каталога с рабочей копией плагина:

dsh plugin --profile web add .

Затем перезапустить DSH (клиентский бандл попадает на страницу при пересборке boot-графа) и обновить страницу GUI.

Проверка

node --test "test/*.test.mjs"                                     # 43 теста
dsh --profile web --dump-config | Select-String "sidebar-pins"    # строка загрузчика

Прямо над кнопкой «Настройки» появится «📌 ЗАКРЕПЛЁННЫЕ (3)» — панель высотой 30vh с прокруткой; список сессий остаётся выше.

Если панели нет, плагин сам скажет почему — в консоли браузера:

__dshSidebarPins

Объект диагностики (window.__dshSidebarPins) всегда заполнен: paneConnected, paneParent, anchors (якорь и цепочка контейнеров до него), seat/seatLabel (какое место выбрано), skipped (места, отброшенные как row-контейнеры), rail, rows и rowButtons (сколько строк сайдбара видно и на скольких появилась кнопка), styleNode, pinned, ignoredPosition и notes со всеми предупреждениями. Подробный лог включается отдельно:

localStorage.setItem("dsh.sidebar-pins.debug", "1");
location.reload();

Панель к тому же само-восстанавливается без опроса таймером: всё, что может её снести, — это изменение DOM, поэтому наблюдается ровно три цели: тело документа (снятие/перерисовка панели), head (стилевой узел) и класс корня сайдбара (переход список ↔ рейка). Любое из этих изменений возвращает панель и стиль на место.

Настройки (браузер, localStorage)

Ключ Значение
dsh.sidebar-pins.collapsed 1 — панель свёрнута до строки заголовка
dsh.sidebar-pins.show-legacy 1 — не скрывать легаси-поверхности dsh-session-pin
dsh.sidebar-pins.debug 1 — писать подробную диагностику в консоль

dsh.sidebar-pins.position больше не читается: место панели фиксировано. Значение, оставшееся в localStorage от прежней версии, показывается в диагностике как ignoredPosition и ни на что не влияет.

Удаление старого плагина

dsh plugin --profile web remove dsh-session-pin

Порядок важен: сначала убедитесь, что блок и кнопки работают (пины перенесены), потом удаляйте. После удаления в ~/.dsh/settings.yaml останется неиспользуемая секция session-pin — её можно убрать вручную, плагин её не читает.

Ограничения

  • Основной список не переупорядочивается. Закреплённые живут в панели снизу и остаются в обычном списке: браузерный порядок живёт в props-store ядра, недоступном плагинам.
  • Высота панели — до 30vh (сайдбар занимает высоту окна), а не фиксированное число пикселей; если сайдбар короткий, панель отдаёт место, а не выдавливает «Настройки» за край. Свёрнутая (и любая в режиме рейки) панель занимает только строку заголовка.
  • Идентификация строки сессии опирается на React-fiber (__reactFiber$…, React 18) с откатом на любой вложенный span с уникальным заголовком: при дублирующихся заголовках кнопка не появится (вместо угадывания). Кнопка в шапке сессии от этой эвристики не зависит.
  • В режиме рейки строк сессий в DOM нет вообще — там нечего помечать кнопкой, поэтому 📌 в строках появляется при развёрнутом сайдбаре (rows: 0 в диагностике в рейке — это норма, а не сбой).
  • Место панели выводится только из своей метки-якоря и не зависит ни от строк сессий, ни от обёрток. Живой хост добавляет лишний безымянный div вокруг корня сайдбара и по одному div вокруг каждой записи слота подвала, а сам footerActions раскладывает детей в строку. Поэтому вместо фиксированной цепочки собирается весь путь вверх от якоря, и место выбирается по измерению: контейнер должен раскладывать детей столбцом (иначе панель легла бы сбоку от «Настройки»), а панель — оказаться выше строки «Настройки». Уровни выше корня сайдбара не рассматриваются: оттуда панель попала бы над всем сайдбаром. Выбранное место и отброшенные видны в диагностике (seat, seatLabel, skipped), а последнее убежище — конец ближайшего «столбцового» контейнера, то есть панель остаётся внутри сайдбара. Стилевой узел переустанавливается на каждом проходе: панель без стилей разрослась бы по содержимому и выдавила «Настройки» за край.
  • Плагин сознательно не импортирует ни одного внешнего пакета. При установке ссылкой (link:) в профиль это ещё и вынужденно: Node разрешает bare-импорты от реального пути пакета (рабочей копии), где нет node_modules, и вход не загрузился бы с ERR_MODULE_NOT_FOUND. Поэтому схема настроек описана руками (см. ниже) — при установке из npm/git ограничение снято, но ручная схема остаётся, чтобы поведение не зависело от способа установки.

Сериализованная схема

Хостовая половина отдаёт конверт схемы, записанный вручную, — он должен совпадать с тем, что ре-гидратирует встроенный в оболочку schemastery. Проверка:

node scripts/check-schema-envelope.mjs

Скрипт поднимает тот же вендорный код из dsh-client-ui-settings/lib/client.js, ре-гидратирует конверт и проверяет {pinned:["a","b"]}, {}, {pinned:[1]}. Этот же тест есть в наборе (test/host.test.mjs) и пропускается, если оболочки на машине нет. Запускайте после обновления DSH.

Разработка

node --test "test/*.test.mjs"

43 теста: чистые хелперы (нормализация id, разбор легаси-документа, модели строк, словарь, toggle), хост-половина (регистрация, схема, миграция, конверт), цепочка мест от своего якоря (включая лишние обёртки живого хоста и отсутствие строк сессий), отказ от row-контейнеров, посадка над строкой «Настройки», геометрическая проверка и переход к следующему месту, последнее убежище внутри сайдбара, поведение в рейке (только иконка и число), отсутствие перерисовки без изменений, наблюдаемые цели, восстановление снесённых панели и стилевого узла, кнопки в строках (fiber / вложенный заголовок / неоднозначность), делегированный клик, кнопка в шапке, откат на localStorage и размонтирование.