dsh-taskboard 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -213,6 +213,13 @@ node scripts/screenshot.mjs # 重新生成 img/ 截图(需本机 Edge)
213
213
 
214
214
  ## 升级日志
215
215
 
216
+ ### 0.4.4
217
+
218
+ - **修复看板偶尔突然掉样式的问题([ #3](https://github.com/cloader/dsh-taskboard/issues/3))**。排查结论:浏览器里其他插件在后台热更新时,会按"样式归属"做清理,而看板自己的样式表没有归属标记,被误当成别的插件的样式连带删掉了。本次修复后:
219
+ - 看板的样式表明确打上了自己的归属标记,其他插件更新时不会再误删它
220
+ - 看板每 2 秒自动检查一次样式是否完好,即便被误删也会立刻自动恢复——以后遇到掉样式,最多闪一下就自动修好,不用再刷新页面
221
+ - 新增回归测试:样式归属标记生效、误删后 2 秒内自动恢复、样式不会重复注入
222
+
216
223
  ### 0.4.3
217
224
 
218
225
  - **修复折叠侧边栏时入口不收成纯图标(用户反馈)**:收起侧边栏后,shell 把侧栏收成 36×36 图标轨道(layout frame 出现 `data-sidebar-collapsed` 属性、侧栏根节点切换 CSS-Module `*_collapsed` 哈希类),而看板入口仍保持全宽——图标、「任务看板」文字、右侧计数角标挤在窄轨里。修复:折叠信号下入口镜像原生轨道几何(36×36、居中、与原生 newSession 折叠态同款间距),隐藏文字与角标,图标 14→16px 保证可读;双信号选择器(`[data-sidebar-collapsed]` 与 `[class*="_collapsed"]`)沿用 0.4.2 双选择器原则,新旧 shell 通吃;展开态完全不受影响,`aria-label` 保留无障碍提示
package/lib/client.js CHANGED
@@ -1586,15 +1586,46 @@ window.__ModuleLoader__.load({
1586
1586
  .dsh-atb-imp-result { font-size: 12px; color: var(--dsw-alias-state-success-primary, #30a46c); margin-top: 10px; }
1587
1587
  .dsh-atb-badge[data-kind="checklist"] { color: var(--dsw-alias-label-secondary, inherit); }
1588
1588
  `;
1589
- let injected = false;
1590
- /** Inject the stylesheet once (idempotent). */
1589
+ /** Style element id (stable since 0.1.x: hook for tests and debugging). */
1590
+ const STYLE_ID = "dsh-taskboard-styles";
1591
+ /**
1592
+ * Ownership tag for the shell's client-module bookkeeping. The web shell
1593
+ * claims every UNTAGGED `<style>` for whichever plugin module materializes
1594
+ * next (claimStyles in dsh-client-modules), and the client HMR driver
1595
+ * deletes `<style>` tags by this attribute on every rebuilt entry
1596
+ * (removeOwnedStyles in dsh-client-hmr).
1597
+ */
1598
+ const PLUGIN_ID = "dsh-taskboard";
1599
+ /** Per-stylesheet identity, mirroring the shell's own data-plugin-css. */
1600
+ const CSS_TAG = "dsh-taskboard/styles";
1601
+ /**
1602
+ * Ensure the stylesheet lives in <head>: create when absent, re-attach when
1603
+ * removed, always carry the ownership tags.
1604
+ *
1605
+ * 0.4.4 fix (field report: CSS randomly dropped, board renders as unstyled
1606
+ * HTML until refresh): injection runs from cordis apply(), which resolves
1607
+ * AFTER this module's materialization, so the old untagged <style> could be
1608
+ * claimed by ANY sibling plugin module that materialized later (observed
1609
+ * with lazily-materializing profile bundles). That sibling's next HMR
1610
+ * rebuild then deleted OUR stylesheet, and the old module-level `injected`
1611
+ * flag blocked re-injection until a full page refresh. Pre-tagging pins
1612
+ * ownership to this plugin: sibling claims skip tagged styles, and only
1613
+ * THIS plugin's rebuild removes it — the fresh apply re-injects. Idempotency
1614
+ * is DOM-based for the same reason: a module flag cannot see removals,
1615
+ * getElementById can. A found element is re-tagged too, so a leftover
1616
+ * pre-0.4.4 element adopted across a hot swap heals its ownership.
1617
+ */
1591
1618
  function injectStyles() {
1592
- if (injected || typeof document === "undefined") return;
1593
- const style = document.createElement("style");
1594
- style.id = "dsh-taskboard-styles";
1595
- style.textContent = STYLES;
1596
- document.head.append(style);
1597
- injected = true;
1619
+ if (typeof document === "undefined") return;
1620
+ let style = document.getElementById(STYLE_ID);
1621
+ if (style === null) {
1622
+ style = document.createElement("style");
1623
+ style.id = STYLE_ID;
1624
+ style.textContent = STYLES;
1625
+ document.head.append(style);
1626
+ }
1627
+ style.dataset.plugin = PLUGIN_ID;
1628
+ style.dataset.pluginCss = CSS_TAG;
1598
1629
  }
1599
1630
 
1600
1631
  //#endregion
@@ -1834,7 +1865,7 @@ window.__ModuleLoader__.load({
1834
1865
  * @module dsh-taskboard/shared/version
1835
1866
  */
1836
1867
  /** The package version (must equal package.json "version"). */
1837
- const PLUGIN_VERSION = "0.4.3";
1868
+ const PLUGIN_VERSION = "0.4.4";
1838
1869
 
1839
1870
  //#endregion
1840
1871
  //#region src/client/board/TaskCard.tsx
@@ -4700,6 +4731,9 @@ window.__ModuleLoader__.load({
4700
4731
  function apply(ctx) {
4701
4732
  try {
4702
4733
  injectStyles();
4734
+ const styleWatch = setInterval(() => {
4735
+ injectStyles();
4736
+ }, 2e3);
4703
4737
  const controller = new BoardController(createClient());
4704
4738
  const connection = ctx.get?.("connection");
4705
4739
  if (connection !== void 0) {
@@ -4743,6 +4777,7 @@ window.__ModuleLoader__.load({
4743
4777
  console.error("[dsh-taskboard] mount failed:", error);
4744
4778
  }
4745
4779
  ctx.effect?.(() => () => {
4780
+ clearInterval(styleWatch);
4746
4781
  for (const d of disposers.splice(0)) d();
4747
4782
  controller.dispose();
4748
4783
  }, "dsh-taskboard: client mount");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-taskboard",
3
3
  "description": "Agent-first task board for the DSH web GUI: host-authoritative task ledger with taskboard_* agent tools, project (= workspace) claim boundaries, per-task model execution in fresh sessions, optional per-task git-worktree isolation (dedicated task branches, commit evidence, one-click merge), host-side cron scheduling, and a live SSE kanban view. Mounts via the official dsh plugin system — no DSH source changes.",
4
- "version": "0.4.3",
4
+ "version": "0.4.4",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -49,6 +49,11 @@ interface ClientContextFace {
49
49
  export function apply(ctx: ClientContextFace): void {
50
50
  try {
51
51
  injectStyles()
52
+ // Stylesheet watchdog (0.4.4): re-assert the stylesheet if anything
53
+ // removed it mid-session — defense-in-depth on top of the ownership
54
+ // tag (see styles.ts for the claim/reload mechanism it guards against).
55
+ // One getElementById per 2s tick; cleared with the plugin teardown.
56
+ const styleWatch = setInterval(() => { injectStyles() }, 2_000)
52
57
  const client = createClient()
53
58
  const controller = new BoardController(client)
54
59
 
@@ -104,7 +109,12 @@ export function apply(ctx: ClientContextFace): void {
104
109
  // cordis effect semantics: the callback runs immediately and its RETURN
105
110
  // VALUE is the disposer (family-plugin precedent: () => () => {...}).
106
111
  // A single-layer arrow here executes the teardown immediately.
112
+ // The stylesheet itself is NOT removed here: the HMR driver owns removal
113
+ // of tagged styles on this plugin's rebuild (and a self-removal could
114
+ // race a same-lifetime re-apply); its rules are dsh-atb-* scoped, so a
115
+ // leftover tag after a full disable is inert.
107
116
  ctx.effect?.(() => () => {
117
+ clearInterval(styleWatch)
108
118
  for (const d of disposers.splice(0)) d()
109
119
  controller.dispose()
110
120
  }, 'dsh-taskboard: client mount')
@@ -691,14 +691,47 @@ html[data-dsh-atb-active] .dsh-atb-view { display: flex; flex-direction: column;
691
691
  .dsh-atb-badge[data-kind="checklist"] { color: var(--dsw-alias-label-secondary, inherit); }
692
692
  `
693
693
 
694
- let injected = false
694
+ /** Style element id (stable since 0.1.x: hook for tests and debugging). */
695
+ const STYLE_ID = 'dsh-taskboard-styles'
695
696
 
696
- /** Inject the stylesheet once (idempotent). */
697
+ /**
698
+ * Ownership tag for the shell's client-module bookkeeping. The web shell
699
+ * claims every UNTAGGED `<style>` for whichever plugin module materializes
700
+ * next (claimStyles in dsh-client-modules), and the client HMR driver
701
+ * deletes `<style>` tags by this attribute on every rebuilt entry
702
+ * (removeOwnedStyles in dsh-client-hmr).
703
+ */
704
+ const PLUGIN_ID = 'dsh-taskboard'
705
+
706
+ /** Per-stylesheet identity, mirroring the shell's own data-plugin-css. */
707
+ const CSS_TAG = 'dsh-taskboard/styles'
708
+
709
+ /**
710
+ * Ensure the stylesheet lives in <head>: create when absent, re-attach when
711
+ * removed, always carry the ownership tags.
712
+ *
713
+ * 0.4.4 fix (field report: CSS randomly dropped, board renders as unstyled
714
+ * HTML until refresh): injection runs from cordis apply(), which resolves
715
+ * AFTER this module's materialization, so the old untagged <style> could be
716
+ * claimed by ANY sibling plugin module that materialized later (observed
717
+ * with lazily-materializing profile bundles). That sibling's next HMR
718
+ * rebuild then deleted OUR stylesheet, and the old module-level `injected`
719
+ * flag blocked re-injection until a full page refresh. Pre-tagging pins
720
+ * ownership to this plugin: sibling claims skip tagged styles, and only
721
+ * THIS plugin's rebuild removes it — the fresh apply re-injects. Idempotency
722
+ * is DOM-based for the same reason: a module flag cannot see removals,
723
+ * getElementById can. A found element is re-tagged too, so a leftover
724
+ * pre-0.4.4 element adopted across a hot swap heals its ownership.
725
+ */
697
726
  export function injectStyles(): void {
698
- if (injected || typeof document === 'undefined') return
699
- const style = document.createElement('style')
700
- style.id = 'dsh-taskboard-styles'
701
- style.textContent = STYLES
702
- document.head.append(style)
703
- injected = true
727
+ if (typeof document === 'undefined') return
728
+ let style = document.getElementById(STYLE_ID)
729
+ if (style === null) {
730
+ style = document.createElement('style')
731
+ style.id = STYLE_ID
732
+ style.textContent = STYLES
733
+ document.head.append(style)
734
+ }
735
+ style.dataset.plugin = PLUGIN_ID
736
+ style.dataset.pluginCss = CSS_TAG
704
737
  }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  /** The package version (must equal package.json "version"). */
9
- export const PLUGIN_VERSION = '0.4.3'
9
+ export const PLUGIN_VERSION = '0.4.4'