sh3-server 0.8.2 → 0.9.1

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.
Files changed (42) hide show
  1. package/app/assets/index-CfqiA9Wt.js +17 -0
  2. package/app/assets/index-CfqiA9Wt.js.map +1 -0
  3. package/app/assets/index-TUefqqjg.css +1 -0
  4. package/app/index.html +2 -2
  5. package/dist/caller.d.ts +2 -1
  6. package/dist/caller.js +2 -1
  7. package/dist/doc-store/conflicts.d.ts +19 -0
  8. package/dist/doc-store/conflicts.js +79 -0
  9. package/dist/doc-store/index.d.ts +11 -0
  10. package/dist/doc-store/index.js +22 -0
  11. package/dist/doc-store/meta.d.ts +11 -0
  12. package/dist/doc-store/meta.js +37 -0
  13. package/dist/doc-store/policy.d.ts +15 -0
  14. package/dist/doc-store/policy.js +85 -0
  15. package/dist/doc-store/reserved.d.ts +7 -0
  16. package/dist/doc-store/reserved.js +26 -0
  17. package/dist/doc-store/roles.d.ts +12 -0
  18. package/dist/doc-store/roles.js +15 -0
  19. package/dist/doc-store/store.d.ts +71 -0
  20. package/dist/doc-store/store.js +336 -0
  21. package/dist/doc-store/tick.d.ts +13 -0
  22. package/dist/doc-store/tick.js +52 -0
  23. package/dist/fs-backend.d.ts +1 -1
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +30 -11
  26. package/dist/keys.d.ts +4 -2
  27. package/dist/keys.js +18 -3
  28. package/dist/migrations/sync-grants.d.ts +7 -0
  29. package/dist/migrations/sync-grants.js +27 -0
  30. package/dist/packages.d.ts +3 -4
  31. package/dist/packages.js +5 -5
  32. package/dist/routes/docs.d.ts +11 -7
  33. package/dist/routes/docs.js +88 -122
  34. package/dist/routes/keys.js +4 -2
  35. package/dist/scope.d.ts +2 -0
  36. package/dist/scope.js +20 -0
  37. package/dist/shard-router.d.ts +8 -9
  38. package/dist/shard-router.js +114 -62
  39. package/package.json +1 -1
  40. package/app/assets/index-Cb-zoqb1.js +0 -17
  41. package/app/assets/index-Cb-zoqb1.js.map +0 -1
  42. package/app/assets/index-DPcN5Lor.css +0 -1
@@ -0,0 +1 @@
1
+ .modal-frame.svelte-2tcvcm{position:absolute;inset:0;display:grid;place-items:center;pointer-events:auto}.modal-box.svelte-2tcvcm{background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated));color:var(--shell-fg);border:1px solid var(--shell-border-strong);border-radius:var(--shell-radius);min-width:320px;max-width:min(640px,90vw);max-height:90vh;overflow:auto;box-shadow:0 20px 48px #00000080;outline:none}.popup-frame.svelte-mp81cl{position:absolute;background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated));color:var(--shell-fg);border:1px solid var(--shell-border-strong);border-radius:var(--shell-radius-sm);box-shadow:0 8px 24px #0006;min-width:120px;outline:none;pointer-events:auto}.toast.svelte-12gwnj0{pointer-events:auto;display:flex;align-items:center;gap:var(--shell-pad-md);padding:var(--shell-pad-sm) var(--shell-pad-md);background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated));color:var(--shell-fg);border:1px solid var(--shell-border-strong);border-left-width:3px;border-radius:var(--shell-radius-sm);box-shadow:0 8px 20px #0006;font-size:12px;min-width:220px;max-width:360px;cursor:pointer;animation:svelte-12gwnj0-toast-in .16s ease-out both}.toast-level.svelte-12gwnj0{text-transform:uppercase;font-family:var(--shell-font-mono);font-size:10px;letter-spacing:.5px;color:var(--shell-fg-muted)}.toast-message.svelte-12gwnj0{flex:1}.toast-info.svelte-12gwnj0{border-left-color:var(--shell-accent)}.toast-success.svelte-12gwnj0{border-left-color:#5cb176}.toast-warn.svelte-12gwnj0{border-left-color:#d6a84a}.toast-error.svelte-12gwnj0{border-left-color:#d06060}@keyframes svelte-12gwnj0-toast-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.shell-title.svelte-1ifjvh5{display:inline-block;line-height:0;overflow:hidden}.shell-title.svelte-1ifjvh5 canvas{transform:scale(var(--shell-title-scale));transform-origin:top left;display:block}.shell-title-fallback.svelte-1ifjvh5{margin:0;font-size:42px;color:var(--shell-accent);letter-spacing:2px}.shell-home.svelte-cpn2x2{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;padding:48px 24px;overflow:auto;background:var(--shell-grad-bg, var(--shell-bg));color:var(--shell-fg);font-family:system-ui,sans-serif}.shell-home-header.svelte-cpn2x2{text-align:center;margin-bottom:24px;display:flex;flex-direction:column;align-items:center;gap:12px}.shell-home-title-row.svelte-cpn2x2{display:flex;align-items:baseline;gap:6px}.shell-home-credit.svelte-cpn2x2{font-size:11px;color:var(--shell-fg-muted);letter-spacing:.04em;margin-top:-4px}.shell-home-credit.svelte-cpn2x2 a:where(.svelte-cpn2x2){color:var(--shell-fg-subtle);text-decoration:none;border-bottom:1px dotted var(--shell-fg-muted)}.shell-home-credit.svelte-cpn2x2 a:where(.svelte-cpn2x2):hover{color:var(--shell-accent);border-bottom-color:var(--shell-accent)}.shell-home-version.svelte-cpn2x2{font-size:14px;color:var(--shell-fg-subtle);letter-spacing:.04em}.shell-home-alpha.svelte-cpn2x2{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#fff;background:var(--shell-accent);padding:2px 8px;border-radius:8px;position:relative;top:-1px}.shell-home-filter.svelte-cpn2x2{width:100%;max-width:720px;margin-bottom:24px}.shell-home-filter-input.svelte-cpn2x2{width:100%;padding:10px 14px;font:inherit;font-size:14px;color:var(--shell-fg);background:var(--shell-bg-elevated);border:1px solid var(--shell-border);border-radius:var(--shell-radius-md);outline:none;transition:border-color .12s ease,box-shadow .12s ease}.shell-home-filter-input.svelte-cpn2x2::placeholder{color:var(--shell-fg-muted)}.shell-home-filter-input.svelte-cpn2x2:focus{border-color:var(--shell-accent);box-shadow:0 0 0 2px color-mix(in srgb,var(--shell-accent) 25%,transparent)}.shell-home-empty.svelte-cpn2x2{color:var(--shell-fg-muted);font-style:italic}.shell-home-section.svelte-cpn2x2{width:100%;max-width:720px;margin-bottom:28px}.shell-home-section-title.svelte-cpn2x2{font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--shell-fg-subtle);margin:0 0 12px}.shell-home-grid.svelte-cpn2x2{display:grid;grid-template-columns:repeat(auto-fill,minmax(80px,1fr));gap:10px}.shell-home-card.svelte-cpn2x2{aspect-ratio:1 / 1;display:flex;flex-direction:column;justify-content:space-between;text-align:left;padding:10px;background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated));border:1px solid var(--shell-border);border-radius:var(--shell-radius-md);color:inherit;font:inherit;cursor:pointer;box-shadow:0 2px 6px #00000040,0 1px 2px #00000026;transition:transform .12s ease,border-color .12s ease,box-shadow .12s ease,background .12s ease}.shell-home-card.svelte-cpn2x2:hover{border-color:var(--shell-accent);transform:translateY(-1px);box-shadow:0 6px 14px #0000004d,0 0 0 1px color-mix(in srgb,var(--shell-accent) 35%,transparent),0 4px 12px color-mix(in srgb,var(--shell-accent) 18%,transparent)}.shell-home-card.svelte-cpn2x2:focus-visible{outline:none;border-color:var(--shell-accent);box-shadow:0 0 0 2px color-mix(in srgb,var(--shell-accent) 40%,transparent)}.shell-home-card.svelte-cpn2x2:active{transform:translateY(0)}.shell-home-card-label.svelte-cpn2x2{font-weight:600;font-size:12px;line-height:1.2;overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2}.shell-home-card-meta.svelte-cpn2x2{display:flex;flex-direction:column;gap:1px;font-size:9px;color:var(--shell-fg-subtle);min-width:0}.shell-home-card-id.svelte-cpn2x2{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.shell-home-card-version.svelte-cpn2x2{color:var(--shell-fg-muted)}.keys-peers.svelte-19l5m7c{padding:24px;font-family:system-ui,sans-serif;color:var(--shell-fg)}.keys-peers-header.svelte-19l5m7c{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.keys-peers-header.svelte-19l5m7c h2:where(.svelte-19l5m7c){margin:0;font-size:18px}.keys-peers-list.svelte-19l5m7c{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.keys-peers-item.svelte-19l5m7c{display:flex;justify-content:space-between;align-items:flex-start;padding:12px 16px;background:var(--shell-bg-elevated, #252540);border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius, 6px);gap:12px}.keys-peers-info.svelte-19l5m7c{display:flex;flex-direction:column;gap:3px;min-width:0}.keys-peers-label.svelte-19l5m7c{font-weight:600}.keys-peers-meta.svelte-19l5m7c{font-size:11px;color:var(--shell-fg-subtle)}.keys-peers-actions.svelte-19l5m7c{display:flex;gap:6px;flex-shrink:0;align-items:flex-start;padding-top:2px}.keys-peers-btn-danger.svelte-19l5m7c{background:transparent;color:var(--shell-error, #d32f2f);border:1px solid var(--shell-error, #d32f2f);font-size:12px;padding:4px 10px;border-radius:var(--shell-radius, 6px);cursor:pointer}.keys-peers-btn-secondary.svelte-19l5m7c{background:transparent;color:var(--shell-fg-subtle);border:1px solid var(--shell-border, #3a3a5c);font-size:12px;padding:4px 10px;border-radius:var(--shell-radius, 6px);cursor:pointer}.keys-peers-muted.svelte-19l5m7c{color:var(--shell-fg-muted);font-style:italic}.keys-peers-error.svelte-19l5m7c{color:var(--shell-error, #d32f2f);font-size:13px}.shell-text.svelte-ypr8s{margin:0;padding:0 8px;font-family:var(--shell-font-mono, monospace);white-space:pre-wrap;word-break:break-word;color:var(--shell-fg, #ddd)}.shell-text.stderr.svelte-ypr8s{color:var(--shell-fg-error, #f88)}.shell-prompt.svelte-u0gb59{padding:4px 8px 0;font-family:var(--shell-font-mono, monospace);display:flex;gap:8px}.shell-prompt-cwd.svelte-u0gb59{color:var(--shell-fg-muted, #888)}.shell-prompt-arrow.svelte-u0gb59{color:var(--shell-accent, #6cf)}.shell-prompt-line.svelte-u0gb59{color:var(--shell-fg, #ddd)}.shell-status.svelte-nfxdpt{padding:2px 8px;font-family:var(--shell-font-mono, monospace);font-style:italic}.shell-status.info.svelte-nfxdpt{color:var(--shell-fg-muted, #888)}.shell-status.warn.svelte-nfxdpt{color:var(--shell-fg-warn, #fc6)}.shell-status.error.svelte-nfxdpt{color:var(--shell-fg-error, #f88)}.shell-rich.svelte-1rnhl05{padding:4px 8px}.shell-scrollback.svelte-isy3jt{flex:1 1 auto;overflow-y:auto;background:var(--shell-bg, #111);color:var(--shell-fg, #ddd)}.shell-input.svelte-1dfv2gk{display:flex;gap:8px;padding:4px 8px;border-top:1px solid var(--shell-border, #333);font-family:var(--shell-font-mono, monospace)}.shell-input-cwd.svelte-1dfv2gk{color:var(--shell-fg-muted, #888)}.shell-input-arrow.svelte-1dfv2gk{color:var(--shell-accent, #6cf)}.shell-input-field.svelte-1dfv2gk{flex:1 1 auto;background:transparent;border:0;outline:0;color:var(--shell-fg, #ddd);font:inherit}.shell-input.locked.svelte-1dfv2gk .shell-input-field:where(.svelte-1dfv2gk){opacity:.5;cursor:default}.toolbar.svelte-gnowwz{display:flex;align-items:center;gap:6px;padding:4px 6px;background:var(--shell-bg-elevated, var(--shell-bg, #1a1a1a));border-bottom:1px solid var(--shell-border, #333);flex-shrink:0}.toolbar-slots.svelte-gnowwz{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.mode-bar.svelte-1i4cqlj{display:inline-flex;gap:2px;padding:1px;border:1px solid var(--shell-border, #444);border-radius:3px}.mode-btn.svelte-1i4cqlj{background:none;border:none;color:var(--shell-fg-dim, var(--shell-fg-muted, #888));padding:2px 8px;border-radius:2px;cursor:pointer;font-size:.85em;line-height:1.4}.mode-btn.svelte-1i4cqlj:hover{background:var(--shell-hover, color-mix(in srgb, var(--shell-fg, #ddd) 10%, transparent));color:var(--shell-fg, #ddd)}.mode-btn.active.svelte-1i4cqlj{background:var(--shell-accent, #7c7cf0);color:var(--shell-bg, #1a1a2e)}.mode-label.svelte-1i4cqlj{font-size:.85em;color:var(--shell-fg-dim, var(--shell-fg-muted, #888))}.focus-lock-btn.svelte-1q6gbap{background:none;border:1px solid var(--shell-border, #444);border-radius:3px;cursor:pointer;padding:2px 5px;font-size:.9em;line-height:1}.focus-lock-btn.svelte-1q6gbap:hover{background:var(--shell-hover, #222)}.target-shard.svelte-1nn2m6x{font-size:.85em;color:var(--shell-fg-dim, #888);font-family:monospace}.shell-terminal.svelte-13komre{display:flex;flex-direction:column;height:100%;background:var(--shell-bg, #111);color:var(--shell-fg, #ddd)}.shell-rich-help.svelte-m248jy table:where(.svelte-m248jy){border-collapse:collapse;width:100%}.shell-rich-help.svelte-m248jy th:where(.svelte-m248jy),.shell-rich-help.svelte-m248jy td:where(.svelte-m248jy){padding:2px 8px;text-align:left}.shell-rich-help.svelte-m248jy button:where(.svelte-m248jy){background:none;border:0;color:var(--shell-link, #6cf);cursor:pointer;padding:0;font:inherit}.shell-rich-help.svelte-m248jy button:where(.svelte-m248jy):hover{text-decoration:underline}.shell-rich-history.svelte-o8m437 ol:where(.svelte-o8m437){list-style-position:inside;margin:0;padding:0}.shell-rich-history.svelte-o8m437 li:where(.svelte-o8m437){padding:2px 0}.shell-rich-history.svelte-o8m437 button:where(.svelte-o8m437){background:none;border:0;color:var(--shell-link, #6cf);cursor:pointer;padding:0;font:inherit;text-align:left}.shell-rich-history.svelte-o8m437 button:where(.svelte-o8m437):hover{text-decoration:underline}.shell-rich-apps.svelte-1u5krjz table:where(.svelte-1u5krjz){border-collapse:collapse;width:100%}.shell-rich-apps.svelte-1u5krjz th:where(.svelte-1u5krjz),.shell-rich-apps.svelte-1u5krjz td:where(.svelte-1u5krjz){padding:2px 8px;text-align:left}.shell-rich-apps.svelte-1u5krjz button:where(.svelte-1u5krjz){background:none;border:0;color:var(--shell-link, #6cf);cursor:pointer;padding:0;font:inherit}.shell-rich-apps.svelte-1u5krjz button:where(.svelte-1u5krjz):hover{text-decoration:underline}.shell-rich-appcard.svelte-npcp8i{padding:8px;border:1px solid var(--shell-border, #444)}.shell-rich-appcard.svelte-npcp8i h3:where(.svelte-npcp8i){margin:0 0 8px}.shell-rich-appcard.svelte-npcp8i p:where(.svelte-npcp8i){margin:4px 0}.shell-rich-shards.svelte-sc3ux4 table:where(.svelte-sc3ux4){border-collapse:collapse;width:100%}.shell-rich-shards.svelte-sc3ux4 th:where(.svelte-sc3ux4),.shell-rich-shards.svelte-sc3ux4 td:where(.svelte-sc3ux4){padding:2px 8px;text-align:left}.shell-rich-views.svelte-15elbr1 table:where(.svelte-15elbr1){border-collapse:collapse;width:100%}.shell-rich-views.svelte-15elbr1 th:where(.svelte-15elbr1),.shell-rich-views.svelte-15elbr1 td:where(.svelte-15elbr1){padding:2px 8px;text-align:left}.shell-rich-views.svelte-15elbr1 button:where(.svelte-15elbr1){background:none;border:0;color:var(--shell-link, #6cf);cursor:pointer;padding:0;font:inherit}.shell-rich-views.svelte-15elbr1 button:where(.svelte-15elbr1):hover{text-decoration:underline}.shell-rich-zones.svelte-1w7onag table:where(.svelte-1w7onag){border-collapse:collapse;width:100%}.shell-rich-zones.svelte-1w7onag th:where(.svelte-1w7onag),.shell-rich-zones.svelte-1w7onag td:where(.svelte-1w7onag){padding:2px 8px;text-align:left}.shell-rich-zonetree.svelte-13iiyr1 pre:where(.svelte-13iiyr1){overflow:auto;padding:8px;background:var(--shell-bg, #000);color:var(--shell-fg, #fff)}.shell-rich-env.svelte-1ox8vog table:where(.svelte-1ox8vog){border-collapse:collapse;width:100%}.shell-rich-env.svelte-1ox8vog th:where(.svelte-1ox8vog),.shell-rich-env.svelte-1ox8vog td:where(.svelte-1ox8vog){padding:2px 8px;text-align:left}.perm-modal-backdrop.svelte-1ipp1r6{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.perm-modal-panel.svelte-1ipp1r6{background:var(--shell-bg, #1e1e1e);color:var(--shell-fg, #e0e0e0);border:1px solid var(--shell-border, #444);border-radius:var(--shell-radius-md);box-shadow:0 8px 32px #0006;min-width:360px;max-width:520px;max-height:80vh;display:flex;flex-direction:column;font-family:var(--shell-font-ui)}.perm-modal-header.svelte-1ipp1r6{padding:16px 20px 12px;border-bottom:1px solid var(--shell-border, #444)}.perm-modal-header.svelte-1ipp1r6 h3:where(.svelte-1ipp1r6){margin:0 0 4px;font-size:1.0625rem;font-weight:600}.perm-modal-subtitle.svelte-1ipp1r6{font-size:.8125rem;color:var(--shell-fg-muted, #888)}.perm-modal-author.svelte-1ipp1r6{margin-left:8px}.perm-modal-body.svelte-1ipp1r6{padding:16px 20px;overflow-y:auto;flex:1}.perm-modal-intro.svelte-1ipp1r6{margin:0 0 8px;font-size:.875rem}.perm-modal-empty.svelte-1ipp1r6{margin:0;font-size:.875rem;color:var(--shell-fg-muted, #888);font-style:italic}.perm-modal-list.svelte-1ipp1r6{list-style:none;margin:0 0 12px;padding:0;display:flex;flex-direction:column;gap:8px}.perm-modal-item.svelte-1ipp1r6{padding:8px 10px;background:var(--shell-input-bg, #2a2a2a);border:1px solid var(--shell-border, #444);border-radius:var(--shell-radius-sm)}.perm-modal-item-title.svelte-1ipp1r6{font-size:.875rem;font-weight:600}.perm-modal-item-desc.svelte-1ipp1r6{font-size:.75rem;color:var(--shell-fg-muted, #888);margin-top:2px}.perm-modal-added.svelte-1ipp1r6 .perm-modal-item:where(.svelte-1ipp1r6){border-color:color-mix(in srgb,var(--shell-warning, #ff9800) 60%,var(--shell-border, #444))}.perm-modal-removed.svelte-1ipp1r6 .perm-modal-item:where(.svelte-1ipp1r6){opacity:.75}.perm-modal-footer.svelte-1ipp1r6{padding:12px 20px;border-top:1px solid var(--shell-border, #444);display:flex;justify-content:flex-end;gap:8px}.perm-modal-cancel.svelte-1ipp1r6{padding:6px 14px;background:transparent;color:var(--shell-fg, #e0e0e0);border:1px solid var(--shell-border, #444);border-radius:var(--shell-radius);font-size:.8125rem;cursor:pointer}.perm-modal-confirm.svelte-1ipp1r6{padding:6px 14px;background:var(--shell-accent, #007acc);color:#fff;border:1px solid var(--shell-accent, #007acc);border-radius:var(--shell-radius);font-size:.8125rem;cursor:pointer;font-weight:600}.store-view.svelte-1aafzt0{font-family:var(--shell-font-ui);color:var(--shell-fg, #e0e0e0);background:var(--shell-bg, #1e1e1e);padding:16px;height:100%;overflow-y:auto;box-sizing:border-box}.store-header.svelte-1aafzt0{margin-bottom:16px}.store-header.svelte-1aafzt0 h2:where(.svelte-1aafzt0){margin:0 0 8px;font-size:1.25rem;font-weight:600}.store-controls.svelte-1aafzt0{display:flex;gap:8px;flex-wrap:wrap}.store-search.svelte-1aafzt0{flex:1;min-width:160px;padding:6px 10px;background:var(--shell-input-bg, #2a2a2a);color:var(--shell-fg, #e0e0e0);border:1px solid var(--shell-border, #444);border-radius:var(--shell-radius);font-family:inherit;font-size:.875rem}.store-search.svelte-1aafzt0::placeholder{color:var(--shell-fg-muted, #888)}.store-filter.svelte-1aafzt0{padding:6px 10px;background:var(--shell-input-bg, #2a2a2a);color:var(--shell-fg, #e0e0e0);border:1px solid var(--shell-border, #444);border-radius:var(--shell-radius);font-family:inherit;font-size:.875rem}.store-refresh.svelte-1aafzt0:disabled{opacity:.6;cursor:not-allowed}.store-error.svelte-1aafzt0{padding:8px 12px;margin-bottom:12px;background:color-mix(in srgb,var(--shell-error, #d32f2f) 15%,transparent);color:var(--shell-error, #d32f2f);border:1px solid var(--shell-error, #d32f2f);border-radius:var(--shell-radius);font-size:.8125rem}.store-grid.svelte-1aafzt0{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}.store-card.svelte-1aafzt0{background:var(--shell-input-bg, #2a2a2a);border:1px solid var(--shell-border, #444);border-radius:var(--shell-radius-md);padding:14px;display:flex;flex-direction:column;gap:8px}.store-card.svelte-1aafzt0:hover{border-color:var(--shell-accent, #007acc)}.store-card-header.svelte-1aafzt0{display:flex;align-items:center;gap:10px}.store-card-icon.svelte-1aafzt0{width:36px;height:36px;flex-shrink:0;display:flex;align-items:center;justify-content:center}.store-icon-img.svelte-1aafzt0{width:36px;height:36px;border-radius:var(--shell-radius);object-fit:cover}.store-icon-placeholder.svelte-1aafzt0{width:36px;height:36px;display:flex;align-items:center;justify-content:center;background:var(--shell-accent, #007acc);color:#fff;border-radius:var(--shell-radius);font-weight:700;font-size:1rem}.store-card-title.svelte-1aafzt0{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.store-card-label.svelte-1aafzt0{font-weight:600;font-size:.9375rem}.store-card-badge.svelte-1aafzt0{font-size:.6875rem;padding:1px 6px;border-radius:var(--shell-radius-sm);text-transform:uppercase;font-weight:600;letter-spacing:.04em}.badge-shard.svelte-1aafzt0{background:color-mix(in srgb,var(--shell-accent, #007acc) 25%,transparent);color:var(--shell-accent, #007acc)}.badge-app.svelte-1aafzt0{background:color-mix(in srgb,var(--shell-success, #4caf50) 25%,transparent);color:var(--shell-success, #4caf50)}.store-card-version.svelte-1aafzt0{font-size:.75rem;color:var(--shell-fg-muted, #888)}.store-card-desc.svelte-1aafzt0{margin:0;font-size:.8125rem;color:var(--shell-fg-muted, #888);line-height:1.4}.store-card-author.svelte-1aafzt0{font-size:.75rem;color:var(--shell-fg-muted, #888)}.store-card-warning.svelte-1aafzt0{font-size:.75rem;color:var(--shell-warning, #ff9800);padding:4px 8px;background:color-mix(in srgb,var(--shell-warning, #ff9800) 10%,transparent);border-radius:var(--shell-radius-sm)}.store-card-actions.svelte-1aafzt0{margin-top:auto;display:flex;justify-content:flex-end}.store-install-btn.svelte-1aafzt0{padding:5px 14px;font-size:.8125rem}.store-install-btn.svelte-1aafzt0:disabled{opacity:.6;cursor:not-allowed}.store-installed-label.svelte-1aafzt0{font-size:.8125rem;color:var(--shell-success, #4caf50);font-weight:600}.store-install-wrap.svelte-1aafzt0{display:flex;flex-direction:column;align-items:flex-end;gap:4px}.store-card-missing.svelte-1aafzt0{font-size:.75rem;color:var(--shell-warning, #ff9800)}.store-update-btn.svelte-1aafzt0{padding:5px 14px;background:var(--shell-warning, #ff9800);font-size:.8125rem}.store-update-btn.svelte-1aafzt0:hover:not(:disabled){filter:brightness(1.1)}.store-update-btn.svelte-1aafzt0:disabled{opacity:.6;cursor:not-allowed}.store-empty.svelte-1aafzt0{text-align:center;padding:32px 16px;color:var(--shell-fg-muted, #888);font-size:.875rem}.store-registries.svelte-1aafzt0{display:flex;flex-direction:column;gap:4px;margin-bottom:8px}.store-registry-entry.svelte-1aafzt0{display:flex;align-items:center;justify-content:space-between;padding:4px 8px;background:var(--shell-input-bg, #2a2a2a);border:1px solid var(--shell-border, #444);border-radius:var(--shell-radius);font-size:.8125rem}.store-registry-url.svelte-1aafzt0{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--shell-fg-muted, #888)}.store-registry-remove.svelte-1aafzt0{padding:2px 8px;background:transparent;color:var(--shell-error, #d32f2f);border:1px solid var(--shell-error, #d32f2f);border-radius:var(--shell-radius-sm);font-size:.75rem;flex-shrink:0;margin-left:8px}.store-add-registry.svelte-1aafzt0{display:flex;gap:8px;margin-bottom:12px}.store-registry-input.svelte-1aafzt0{flex:1;padding:6px 10px;background:var(--shell-input-bg, #2a2a2a);color:var(--shell-fg, #e0e0e0);border:1px solid var(--shell-border, #444);border-radius:var(--shell-radius);font-family:inherit;font-size:.8125rem}.store-registry-input.svelte-1aafzt0::placeholder{color:var(--shell-fg-muted, #888)}.store-add-btn.svelte-1aafzt0{font-size:.8125rem;white-space:nowrap}.store-add-btn.svelte-1aafzt0:disabled{opacity:.6;cursor:not-allowed}.installed-view.svelte-1hqclkp{font-family:var(--shell-font-ui);color:var(--shell-fg, #e0e0e0);background:var(--shell-bg, #1e1e1e);padding:16px;height:100%;overflow-y:auto;box-sizing:border-box}.installed-header.svelte-1hqclkp{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}.installed-header.svelte-1hqclkp h2:where(.svelte-1hqclkp){margin:0;font-size:1.25rem;font-weight:600}.installed-empty.svelte-1hqclkp{text-align:center;padding:32px 16px;color:var(--shell-fg-muted, #888);font-size:.875rem}.installed-list.svelte-1hqclkp{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.installed-item.svelte-1hqclkp{background:var(--shell-input-bg, #2a2a2a);border:1px solid var(--shell-border, #444);border-radius:var(--shell-radius-md);padding:12px 14px;display:flex;flex-direction:column;gap:6px}.installed-item-main.svelte-1hqclkp{display:flex;align-items:center;gap:8px}.installed-item-id.svelte-1hqclkp{font-weight:600;font-size:.9375rem}.installed-item-badge.svelte-1hqclkp{font-size:.6875rem;padding:1px 6px;border-radius:var(--shell-radius-sm);text-transform:uppercase;font-weight:600;letter-spacing:.04em}.badge-shard.svelte-1hqclkp{background:color-mix(in srgb,var(--shell-accent, #007acc) 25%,transparent);color:var(--shell-accent, #007acc)}.badge-app.svelte-1hqclkp{background:color-mix(in srgb,var(--shell-success, #4caf50) 25%,transparent);color:var(--shell-success, #4caf50)}.installed-item-version.svelte-1hqclkp{font-size:.75rem;color:var(--shell-fg-muted, #888)}.installed-item-meta.svelte-1hqclkp{display:flex;gap:16px;flex-wrap:wrap;font-size:.75rem;color:var(--shell-fg-muted, #888)}.installed-item-actions.svelte-1hqclkp{display:flex;justify-content:flex-end;gap:8px}.installed-uninstall-btn.svelte-1hqclkp{padding:4px 12px;background:transparent;color:var(--shell-error, #d32f2f);border:1px solid var(--shell-error, #d32f2f);font-size:.8125rem}.installed-uninstall-btn.svelte-1hqclkp:hover:not(:disabled){background:color-mix(in srgb,var(--shell-error, #d32f2f) 15%,transparent)}.installed-uninstall-btn.svelte-1hqclkp:disabled{opacity:.6;cursor:not-allowed}.installed-update-btn.svelte-1hqclkp{padding:4px 12px;background:var(--shell-warning, #ff9800);font-size:.8125rem}.installed-update-btn.svelte-1hqclkp:hover:not(:disabled){filter:brightness(1.1)}.installed-update-btn.svelte-1hqclkp:disabled{opacity:.6;cursor:not-allowed}.installed-error.svelte-1hqclkp{padding:8px 12px;margin-bottom:12px;background:color-mix(in srgb,var(--shell-error, #d32f2f) 15%,transparent);color:var(--shell-error, #d32f2f);border:1px solid var(--shell-error, #d32f2f);border-radius:var(--shell-radius);font-size:.8125rem}.admin-users.svelte-4q792t{padding:24px;font-family:system-ui,sans-serif;color:var(--shell-fg)}.admin-users-header.svelte-4q792t{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.admin-users-header.svelte-4q792t h2:where(.svelte-4q792t){margin:0;font-size:18px}.admin-create-form.svelte-4q792t,.admin-edit-form.svelte-4q792t{display:flex;flex-direction:column;gap:8px;margin-bottom:16px;max-width:400px}.admin-input.svelte-4q792t{padding:8px 12px;background:var(--shell-bg, #1a1a2e);color:var(--shell-fg);border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius, 6px);font-size:13px}.admin-btn.svelte-4q792t{font-weight:600;font-size:13px}.admin-btn.svelte-4q792t:disabled{opacity:.6;cursor:not-allowed}.admin-btn-secondary.svelte-4q792t{background:transparent;color:var(--shell-fg-subtle);border:1px solid var(--shell-border);font-size:12px}.admin-btn-danger.svelte-4q792t{background:transparent;color:var(--shell-error, #d32f2f);border:1px solid var(--shell-error, #d32f2f);font-size:12px}.admin-user-list.svelte-4q792t{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.admin-user-item.svelte-4q792t{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:var(--shell-bg-elevated, #252540);border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius, 6px)}.admin-user-info.svelte-4q792t{display:flex;flex-direction:column;gap:2px}.admin-user-name.svelte-4q792t{font-weight:600}.admin-user-meta.svelte-4q792t{font-size:11px;color:var(--shell-fg-subtle)}.admin-user-actions.svelte-4q792t,.admin-edit-actions.svelte-4q792t{display:flex;gap:6px}.admin-error.svelte-4q792t{color:var(--shell-error, #d32f2f);font-size:13px}.admin-muted.svelte-4q792t{color:var(--shell-fg-muted);font-style:italic}.admin-auth.svelte-1hsgdb4{padding:24px;font-family:system-ui,sans-serif;color:var(--shell-fg)}.admin-auth.svelte-1hsgdb4 h2:where(.svelte-1hsgdb4){margin:0 0 16px;font-size:18px}.admin-auth-fields.svelte-1hsgdb4{display:flex;flex-direction:column;gap:16px;max-width:480px}.admin-toggle.svelte-1hsgdb4{display:flex;flex-wrap:wrap;align-items:center;gap:8px;cursor:pointer}.admin-toggle.svelte-1hsgdb4 input:where(.svelte-1hsgdb4){accent-color:var(--shell-accent, #7c7cf0)}.admin-hint.svelte-1hsgdb4{flex-basis:100%;font-size:11px;color:var(--shell-fg-muted);margin-left:24px}.admin-field.svelte-1hsgdb4{display:flex;flex-direction:column;gap:4px}.admin-field.svelte-1hsgdb4 span:where(.svelte-1hsgdb4){font-size:13px}.admin-input.svelte-1hsgdb4{padding:8px 12px;background:var(--shell-bg);color:var(--shell-fg);border:1px solid var(--shell-border);border-radius:var(--shell-radius, 6px);font-size:13px}.admin-input-sm.svelte-1hsgdb4{max-width:120px}.admin-btn.svelte-1hsgdb4{padding:8px 16px;font-weight:600;align-self:flex-start}.admin-btn.svelte-1hsgdb4:disabled{opacity:.6;cursor:not-allowed}.admin-error.svelte-1hsgdb4{margin-top:8px;color:var(--shell-error, #d32f2f);font-size:13px}.admin-muted.svelte-1hsgdb4{color:var(--shell-fg-muted);font-style:italic}.admin-system.svelte-1gkiloq{padding:24px;font-family:system-ui,sans-serif;color:var(--shell-fg)}.admin-system.svelte-1gkiloq h2:where(.svelte-1gkiloq){margin:0 0 16px;font-size:18px}.admin-system.svelte-1gkiloq h3:where(.svelte-1gkiloq){margin:0 0 8px;font-size:14px;text-transform:uppercase;letter-spacing:.05em;color:var(--shell-fg-subtle)}.admin-system-info.svelte-1gkiloq{margin-bottom:24px}.admin-system-row.svelte-1gkiloq{display:flex;gap:12px;padding:8px 0;border-bottom:1px solid var(--shell-border, #3a3a5c);font-size:13px}.admin-system-label.svelte-1gkiloq{color:var(--shell-fg-subtle);min-width:140px}.admin-system-section.svelte-1gkiloq{margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid var(--shell-border, #3a3a5c)}.admin-system-section.svelte-1gkiloq:last-child{border-bottom:none}.admin-system-hint.svelte-1gkiloq{font-size:13px;color:var(--shell-fg-subtle);margin:0 0 12px}.admin-system-readout.svelte-1gkiloq{font-size:13px;margin:8px 0 12px}.admin-system-snaps.svelte-1gkiloq{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px}.admin-snap.svelte-1gkiloq{padding:4px 10px;font-size:12px;background:transparent;border:1px solid var(--shell-border, #3a3a5c);color:var(--shell-fg);cursor:pointer;border-radius:var(--shell-radius-sm, 3px)}.admin-snap.active.svelte-1gkiloq{background:var(--shell-accent, #4a7bd4);color:var(--shell-bg);border-color:var(--shell-accent, #4a7bd4)}.admin-snap.svelte-1gkiloq:disabled{opacity:.5;cursor:not-allowed}.admin-system-actions.svelte-1gkiloq{display:flex;flex-direction:row;gap:8px;align-items:center;flex-wrap:wrap}.admin-btn.svelte-1gkiloq{padding:8px 16px;background:var(--shell-accent, #4a7bd4);color:var(--shell-bg);border:1px solid var(--shell-accent, #4a7bd4);font-weight:600;cursor:pointer;border-radius:var(--shell-radius-sm, 3px)}.admin-btn.svelte-1gkiloq:disabled{opacity:.5;cursor:not-allowed}.admin-btn-ghost.svelte-1gkiloq{padding:8px 16px;background:transparent;color:var(--shell-fg);border:1px solid var(--shell-border, #3a3a5c);cursor:pointer;border-radius:var(--shell-radius-sm, 3px)}.admin-btn-ghost.svelte-1gkiloq:disabled{opacity:.5;cursor:not-allowed}.admin-btn-danger.svelte-1gkiloq{padding:8px 16px;background:transparent;color:var(--shell-error, #d32f2f);border:1px solid var(--shell-error, #d32f2f);font-weight:600;cursor:pointer;border-radius:var(--shell-radius-sm, 3px)}.admin-btn-danger.svelte-1gkiloq:disabled{opacity:.6;cursor:not-allowed}.admin-error.svelte-1gkiloq{color:var(--shell-error, #d32f2f);font-size:13px}input[type=range].svelte-1gkiloq{width:100%;max-width:480px}.admin-keys.svelte-1afg5z5{padding:24px;font-family:system-ui,sans-serif;color:var(--shell-fg)}.admin-keys-header.svelte-1afg5z5{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.admin-keys-header.svelte-1afg5z5 h2:where(.svelte-1afg5z5){margin:0;font-size:18px}.admin-create-form.svelte-1afg5z5{display:flex;flex-direction:column;gap:8px;margin-bottom:16px;max-width:400px}.admin-input.svelte-1afg5z5{padding:8px 12px;background:var(--shell-bg, #1a1a2e);color:var(--shell-fg);border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius, 6px);font-size:13px}.admin-btn.svelte-1afg5z5{font-weight:600;font-size:13px}.admin-btn.svelte-1afg5z5:disabled{opacity:.6;cursor:not-allowed}.admin-btn-secondary.svelte-1afg5z5{background:transparent;color:var(--shell-fg-subtle);border:1px solid var(--shell-border);font-size:12px}.admin-btn-danger.svelte-1afg5z5{background:transparent;color:var(--shell-error, #d32f2f);border:1px solid var(--shell-error, #d32f2f);font-size:12px}.admin-key-list.svelte-1afg5z5{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.admin-key-item.svelte-1afg5z5{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:var(--shell-bg-elevated, #252540);border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius, 6px)}.admin-key-info.svelte-1afg5z5{display:flex;flex-direction:column;gap:2px}.admin-key-label.svelte-1afg5z5{font-weight:600}.admin-key-meta.svelte-1afg5z5{font-size:11px;color:var(--shell-fg-subtle)}.admin-key-value.svelte-1afg5z5{font-size:12px;color:var(--shell-fg-muted);background:var(--shell-bg, #1a1a2e);padding:2px 6px;border-radius:3px;margin-top:2px;word-break:break-all}.admin-key-actions.svelte-1afg5z5{display:flex;gap:6px;flex-shrink:0}.admin-muted.svelte-1afg5z5{color:var(--shell-fg-muted);font-style:italic}.admin-error.svelte-1afg5z5{color:var(--shell-error, #d32f2f);font-size:13px}button,input[type=button],input[type=submit],input[type=reset],.shell-base-button{padding:6px 14px;background:var(--shell-accent, #6ea8fe);color:#fff;border:none;border-radius:var(--shell-radius);cursor:pointer;font-family:inherit;font-size:.875rem;line-height:var(--shell-line)}button:hover,input[type=button]:hover,input[type=submit]:hover,input[type=reset]:hover,.shell-base-button:hover{filter:brightness(1.12)}button:active,input[type=button]:active,input[type=submit]:active,input[type=reset]:active,.shell-base-button:active{filter:brightness(.92)}.splitter.svelte-1adl4o1{display:flex;width:100%;height:100%;min-width:0;min-height:0}.splitter.horizontal.svelte-1adl4o1{flex-direction:row}.splitter.vertical.svelte-1adl4o1{flex-direction:column}.splitter-pane.svelte-1adl4o1{position:relative;min-width:0;min-height:0;overflow:hidden;display:flex}.horizontal.svelte-1adl4o1>.splitter-pane:where(.svelte-1adl4o1){flex-direction:row}.vertical.svelte-1adl4o1>.splitter-pane:where(.svelte-1adl4o1){flex-direction:column}.splitter-pane.collapsed.svelte-1adl4o1{overflow:visible}.pane-content.svelte-1adl4o1{flex:1 1 0;position:relative;min-width:0;min-height:0;overflow:hidden}.collapse-header.svelte-1adl4o1{appearance:none;flex:0 0 auto;display:flex;align-items:center;justify-content:center;background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated));border:none;color:var(--shell-fg-muted);cursor:pointer;padding:0;font-size:10px}.collapse-header.svelte-1adl4o1:hover{color:var(--shell-fg);background:var(--shell-accent-muted)}body[data-dragging] .collapse-header.svelte-1adl4o1{pointer-events:none}.collapse-header.horizontal.svelte-1adl4o1{width:100%;height:100%;writing-mode:vertical-rl}.collapse-header.vertical.svelte-1adl4o1{width:100%;height:100%}.collapse-header.expanded.horizontal.svelte-1adl4o1{width:16px;height:100%;border-right:1px solid var(--shell-border)}.collapse-header.expanded.vertical.svelte-1adl4o1{width:100%;height:16px;border-bottom:1px solid var(--shell-border)}.splitter-handle.svelte-1adl4o1{flex:0 0 auto;background:var(--shell-border);transition:background-color .12s ease;touch-action:none}.splitter-handle.svelte-1adl4o1:hover,.splitter-handle.dragging.svelte-1adl4o1{background:var(--shell-accent)}body[data-dragging] .splitter-handle.svelte-1adl4o1{pointer-events:none}.splitter-handle.disabled.svelte-1adl4o1{cursor:default;pointer-events:none}.horizontal.svelte-1adl4o1>.splitter-handle:where(.svelte-1adl4o1){width:4px;cursor:col-resize}.vertical.svelte-1adl4o1>.splitter-handle:where(.svelte-1adl4o1){height:4px;cursor:row-resize}.tabbed-panel.svelte-1kxcyhf{display:flex;flex-direction:column;width:100%;height:100%;min-width:0;min-height:0;background:var(--shell-grad-bg, var(--shell-bg))}.tab-strip.svelte-1kxcyhf{position:relative;flex:0 0 auto;display:flex;gap:1px;background:var(--shell-grad-bg-sunken, var(--shell-bg-sunken));border-bottom:1px solid var(--shell-border);padding:0 var(--shell-pad-sm);user-select:none}.tab.svelte-1kxcyhf{appearance:none;background:transparent;border:none;color:var(--shell-fg-muted);font:inherit;font-size:12px;padding:var(--shell-pad-sm) var(--shell-pad-md);margin-top:2px;display:inline-flex;align-items:center;gap:var(--shell-pad-sm);cursor:pointer;border-top:2px solid transparent;border-radius:2px 2px 0 0;touch-action:none}.tab.svelte-1kxcyhf:hover{color:var(--shell-fg);background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated))}.tab.active.svelte-1kxcyhf{color:var(--shell-fg);background:var(--shell-grad-bg, var(--shell-bg));border-top-color:var(--shell-accent)}.tab-icon.svelte-1kxcyhf{font-size:11px}.tab-label.svelte-1kxcyhf{white-space:nowrap}.tab-dirty.svelte-1kxcyhf{width:8px;height:8px;border-radius:50%;background:var(--shell-accent);flex-shrink:0}.tab-close.svelte-1kxcyhf{display:inline-flex;font-size:10px;line-height:1;padding:2px;border-radius:var(--shell-radius-sm);color:var(--shell-fg-muted);cursor:pointer;flex-shrink:0;margin-left:auto}.tab-close.svelte-1kxcyhf:hover{color:var(--shell-fg);background:var(--shell-bg-sunken)}.drop-indicator.svelte-1kxcyhf{position:absolute;width:2px;background:var(--shell-accent);box-shadow:0 0 6px var(--shell-accent);pointer-events:none;border-radius:1px}.tab-body.svelte-1kxcyhf{flex:1 1 auto;position:relative;min-width:0;min-height:0;overflow:hidden}.tab-body-pane.svelte-1kxcyhf{position:absolute;inset:0;min-width:0;min-height:0;display:none}.tab-body-pane.active.svelte-1kxcyhf{display:block}.slot.svelte-1czj0s8{position:relative;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden}.slot-placeholder.svelte-1czj0s8{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--shell-pad-sm);color:var(--shell-fg-muted);font-size:12px;text-align:center;padding:var(--shell-pad-md);background:repeating-linear-gradient(45deg,var(--shell-bg) 0 10px,var(--shell-bg-elevated) 10px 20px);border:1px dashed var(--shell-border-strong);pointer-events:none}.slot-id.svelte-1czj0s8{color:var(--shell-fg);font-family:var(--shell-font-mono);font-size:13px}.slot-meta.svelte-1czj0s8 code:where(.svelte-1czj0s8){font-family:var(--shell-font-mono);color:var(--shell-accent)}.slot-dims.svelte-1czj0s8{font-family:var(--shell-font-mono);color:var(--shell-fg-subtle);font-size:11px}.slot-drop-zone.svelte-re71zu{position:absolute;inset:0;pointer-events:none}.slot-drop-zone.active.svelte-re71zu{pointer-events:auto}.quad-highlight.svelte-re71zu{position:absolute;background:var(--shell-accent);opacity:.18;border:1px dashed var(--shell-accent);pointer-events:none;transition:inset 80ms ease}.quad-highlight.quad-left.svelte-re71zu{inset:0 50% 0 0}.quad-highlight.quad-right.svelte-re71zu{inset:0 0 0 50%}.quad-highlight.quad-top.svelte-re71zu{inset:0 0 50%}.quad-highlight.quad-bottom.svelte-re71zu{inset:50% 0 0}.quad-target.svelte-re71zu{position:absolute;pointer-events:none}.quad-target.quad-left.svelte-re71zu{inset:0 50% 0 0}.quad-target.quad-right.svelte-re71zu{inset:0 0 0 50%}.quad-target.quad-top.svelte-re71zu{inset:0 0 50%}.quad-target.quad-bottom.svelte-re71zu{inset:50% 0 0}.tab-slot-wrapper.svelte-1gw9g38,.leaf-slot-wrapper.svelte-1gw9g38{position:absolute;inset:0;min-width:0;min-height:0}.empty-tabs-placeholder.svelte-1gw9g38{width:100%;height:100%;min-width:0;min-height:0;position:relative}.empty-tabs-default.svelte-1gw9g38{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--shell-fg-muted);font-size:12px;background:repeating-linear-gradient(45deg,var(--shell-bg) 0 10px,var(--shell-bg-elevated) 10px 20px);border:1px dashed var(--shell-border-strong)}.empty-tabs-custom.svelte-1gw9g38{position:absolute;inset:0}.empty-tabs-drop.svelte-1gw9g38{position:absolute;inset:0;pointer-events:auto}.drag-preview.svelte-133fiip{position:absolute;display:inline-flex;align-items:center;gap:var(--shell-pad-sm);padding:var(--shell-pad-sm) var(--shell-pad-md);background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated));color:var(--shell-fg);border:1px solid var(--shell-accent);border-radius:var(--shell-radius-sm);box-shadow:0 8px 24px #00000080;font-size:12px;font-family:var(--shell-font-ui);pointer-events:none;opacity:.9}.drag-preview-icon.svelte-133fiip{font-size:11px}.drag-preview-label.svelte-133fiip{white-space:nowrap}.sh3-float-frame.svelte-1p734al{position:absolute;display:flex;flex-direction:column;background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated, #1e1e1e));color:var(--shell-fg);border:1px solid var(--shell-border-strong);border-radius:var(--shell-radius);box-shadow:0 8px 24px #0006;pointer-events:auto}.sh3-float-header.svelte-1p734al{display:flex;align-items:center;justify-content:space-between;padding:4px 8px;background:var(--shell-bg, #111);cursor:move;user-select:none;border-bottom:1px solid var(--shell-border-strong);border-top-left-radius:var(--shell-radius);border-top-right-radius:var(--shell-radius);flex-shrink:0}.sh3-float-title.svelte-1p734al{font-size:12px;color:var(--shell-fg);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sh3-float-close.svelte-1p734al{background:transparent;border:none;color:var(--shell-fg);font-size:16px;line-height:1;cursor:pointer;padding:0 4px;flex-shrink:0}.sh3-float-body.svelte-1p734al{flex:1;position:relative;overflow:hidden;min-height:0}.sh3-float-layer.svelte-1rzcset{position:absolute;inset:0;pointer-events:none}.guest-banner.svelte-pymkh{display:flex;align-items:center;justify-content:center;gap:12px;padding:6px var(--shell-pad-md, 12px);background:color-mix(in srgb,var(--shell-accent, #7c7cf0) 15%,transparent);border-bottom:1px solid var(--shell-border, #3a3a5c);font-size:12px;color:var(--shell-fg, #e0e0e0)}.guest-banner-action.svelte-pymkh{padding:3px 10px;color:var(--shell-bg, #1a1a2e);font-size:11px;font-weight:600}.guest-signin-overlay.svelte-pymkh{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:#00000080;z-index:9999}.guest-signin-card.svelte-pymkh{display:flex;flex-direction:column;gap:12px;padding:32px;background:var(--shell-bg-elevated, #252540);border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius-lg, 12px);min-width:300px}.guest-signin-form.svelte-pymkh{display:flex;flex-direction:column;gap:10px}.guest-signin-input.svelte-pymkh{padding:8px 12px;background:var(--shell-bg, #1a1a2e);color:var(--shell-fg, #e0e0e0);border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius, 6px);font-size:13px}.guest-signin-input.svelte-pymkh::placeholder{color:var(--shell-fg-muted, #888)}.guest-signin-actions.svelte-pymkh{display:flex;gap:8px}.guest-signin-btn.svelte-pymkh{flex:1;padding:8px;color:var(--shell-bg, #1a1a2e);font-weight:600}.guest-signin-btn.svelte-pymkh:disabled{opacity:.6;cursor:not-allowed}.guest-signin-cancel.svelte-pymkh{padding:8px 12px;background:transparent;color:var(--shell-fg-subtle, #aaa);border:1px solid var(--shell-border, #3a3a5c)}.guest-signin-error.svelte-pymkh{padding:6px 10px;font-size:12px;color:var(--shell-error, #d32f2f);background:color-mix(in srgb,var(--shell-error, #d32f2f) 10%,transparent);border-radius:var(--shell-radius, 6px)}.sh3-consent-backdrop.svelte-14me5sj{position:fixed;inset:0;background:#0000008c;display:flex;align-items:center;justify-content:center;z-index:9999}.sh3-consent-card.svelte-14me5sj{background:var(--shell-bg-elevated, #222);color:var(--shell-fg, #eee);border:1px solid var(--shell-border, #444);padding:1.5rem;border-radius:8px;min-width:360px;max-width:520px;box-shadow:0 8px 32px #0006}.sh3-consent-title.svelte-14me5sj{margin:0 0 1rem;font-size:1rem;font-weight:600;color:var(--shell-fg, #eee)}.sh3-consent-title.svelte-14me5sj span:where(.svelte-14me5sj){color:var(--shell-accent, #7eb8f7)}.sh3-consent-fields.svelte-14me5sj{margin:0 0 1rem;display:grid;grid-template-columns:auto 1fr;column-gap:.75rem;row-gap:.25rem;align-items:baseline}.sh3-consent-fields.svelte-14me5sj dt:where(.svelte-14me5sj){font-weight:600;color:var(--shell-fg-muted, #aaa);font-size:.8rem;text-transform:uppercase;letter-spacing:.05em;padding-top:.35rem}.sh3-consent-fields.svelte-14me5sj dd:where(.svelte-14me5sj){margin:0;padding-top:.35rem;word-break:break-word}.sh3-consent-scopes.svelte-14me5sj{display:flex;flex-wrap:wrap;gap:.25rem}.sh3-consent-scope.svelte-14me5sj{display:inline-block;padding:.1rem .35rem;background:#ffffff14;border:1px solid rgba(255,255,255,.12);border-radius:3px;font-size:.82em;font-family:var(--shell-font-mono, monospace)}.sh3-consent-actions.svelte-14me5sj{display:flex;justify-content:flex-end;gap:.5rem;margin-top:1.25rem}.sh3-consent-deny.svelte-14me5sj,.sh3-consent-approve.svelte-14me5sj{padding:.4rem 1rem;border-radius:4px;font-size:.875rem;cursor:pointer;border:1px solid transparent;transition:opacity .1s}.sh3-consent-deny.svelte-14me5sj{background:transparent;color:var(--shell-fg-muted, #aaa);border-color:var(--shell-border, #444)}.sh3-consent-deny.svelte-14me5sj:hover{color:var(--shell-fg, #eee);border-color:var(--shell-fg-muted, #aaa)}.sh3-consent-approve.svelte-14me5sj{background:var(--shell-accent, #7eb8f7);color:#000;border-color:var(--shell-accent, #7eb8f7)}.sh3-consent-approve.svelte-14me5sj:hover{opacity:.85}.shell.svelte-187fra4{display:grid;grid-template-rows:var(--shell-tabbar-height) auto 1fr var(--shell-statusbar-height);height:100%;width:100%;position:relative;background:var(--shell-grad-bg, var(--shell-bg));color:var(--shell-fg)}.shell-tabbar.svelte-187fra4{display:flex;align-items:center;gap:var(--shell-pad-md);padding:0 var(--shell-pad-md);background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated));border-bottom:1px solid var(--shell-border);user-select:none}.shell-tabbar-brand.svelte-187fra4{font-weight:600;color:var(--shell-accent);letter-spacing:.5px}.shell-content.svelte-187fra4{position:relative;overflow:hidden;background:var(--shell-grad-bg, var(--shell-bg));min-width:0;min-height:0}.shell-statusbar.svelte-187fra4{display:flex;align-items:center;justify-content:space-between;padding:0 var(--shell-pad-md);background:var(--shell-grad-bg-sunken, var(--shell-bg-sunken));border-top:1px solid var(--shell-border);color:var(--shell-fg-muted);font-size:11px;user-select:none}.shell-overlays.svelte-187fra4,.shell-overlay-root.svelte-187fra4{position:absolute;inset:0;pointer-events:none}.shell-tabbar-home-button.svelte-187fra4{display:flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;background:transparent;color:var(--shell-fg-muted);border:1px solid var(--shell-border)}.shell-tabbar-home-button.svelte-187fra4:hover:not(:disabled){color:var(--shell-fg);border-color:var(--shell-fg-muted)}.shell-tabbar-home-button.svelte-187fra4:disabled{color:var(--shell-fg-subtle);border-color:var(--shell-border);cursor:default}.shell-tabbar-home-icon.svelte-187fra4{width:14px;height:14px}.shell-tabbar-user.svelte-187fra4{display:flex;align-items:center;gap:6px;margin-left:auto}.shell-tabbar-user-name.svelte-187fra4{font-size:12px;color:var(--shell-fg-subtle)}.shell-tabbar-tag.svelte-187fra4{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#fff;background:var(--shell-accent);padding:1px 6px;border-radius:6px}.shell-tabbar-signout.svelte-187fra4{display:flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;background:transparent;color:var(--shell-fg-muted);border:1px solid var(--shell-border)}.shell-tabbar-signout.svelte-187fra4:hover{color:var(--shell-fg);border-color:var(--shell-fg-muted)}.shell-tabbar-signout-icon.svelte-187fra4{width:14px;height:14px}.signin-wall.svelte-lh4k15{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:var(--shell-grad-bg, var(--shell-bg, #1a1a2e));color:var(--shell-fg, #e0e0e0);font-family:system-ui,sans-serif}.signin-card.svelte-lh4k15{display:flex;flex-direction:column;align-items:center;gap:16px;padding:48px 40px;background:var(--shell-grad-bg-elevated, var(--shell-bg-elevated, #252540));border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius-lg, 12px);min-width:320px}.signin-brand.svelte-lh4k15{margin:0 0 8px;font-size:42px;color:var(--shell-accent, #7c7cf0);letter-spacing:2px}.signin-form.svelte-lh4k15{display:flex;flex-direction:column;gap:12px;width:100%}.signin-input.svelte-lh4k15{padding:10px 14px;background:var(--shell-bg, #1a1a2e);color:var(--shell-fg, #e0e0e0);border:1px solid var(--shell-border, #3a3a5c);border-radius:var(--shell-radius, 6px);font-size:14px}.signin-input.svelte-lh4k15::placeholder{color:var(--shell-fg-muted, #888)}.signin-btn.svelte-lh4k15{padding:10px 16px;color:var(--shell-bg, #1a1a2e);font-weight:600;font-size:14px}.signin-btn.svelte-lh4k15:disabled{opacity:.6;cursor:not-allowed}.signin-link.svelte-lh4k15{background:none;color:var(--shell-accent, #7c7cf0);font-size:13px;padding:0}.signin-link.svelte-lh4k15:hover{text-decoration:underline}.signin-error.svelte-lh4k15{padding:8px 12px;font-size:13px;color:var(--shell-error, #d32f2f);background:color-mix(in srgb,var(--shell-error, #d32f2f) 10%,transparent);border-radius:var(--shell-radius, 6px);width:100%;text-align:center}:root{--shell-bg: #1a1b1e;--shell-bg-elevated: #22232a;--shell-bg-sunken: #141518;--shell-border: #2e3038;--shell-border-strong: #3c3f4a;--shell-fg: #e4e6eb;--shell-fg-muted: #9aa0aa;--shell-fg-subtle: #6b7280;--shell-accent: #6ea8fe;--shell-accent-muted: #3a5580;--shell-input-bg: #2a2a2a;--shell-error: #f87171;--shell-warning: #fbbf24;--shell-success: #34d399;--shell-font-ui: system-ui, -apple-system, "Segoe UI", sans-serif;--shell-font-mono: ui-monospace, "Cascadia Code", "Consolas", monospace;--shell-font-size: 13px;--shell-line: 1.45;--shell-radius-sm: 3px;--shell-radius: 4px;--shell-radius-md: 6px;--shell-radius-lg: 8px;--shell-pad-xs: 2px;--shell-pad-sm: 4px;--shell-pad-md: 8px;--shell-pad-lg: 12px;--shell-tabbar-height: 32px;--shell-statusbar-height: 22px;--shell-z-layer-0: 0;--shell-z-layer-1: 100;--shell-z-layer-2: 200;--shell-z-layer-3: 300;--shell-z-layer-4: 400;--shell-z-layer-5: 500;--shell-z-layer-6: 600}*,*:before,*:after{box-sizing:border-box}html,body{margin:0;padding:0;height:100%;overflow:hidden;background:var(--shell-grad-bg, var(--shell-bg));color:var(--shell-fg);font-family:var(--shell-font-ui);font-size:var(--shell-font-size);line-height:var(--shell-line);-webkit-font-smoothing:antialiased}#app{height:100%}
package/app/index.html CHANGED
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>SH3</title>
7
- <script type="module" crossorigin src="/assets/index-Cb-zoqb1.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-DPcN5Lor.css">
7
+ <script type="module" crossorigin src="/assets/index-CfqiA9Wt.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-TUefqqjg.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="app"></div>
package/dist/caller.d.ts CHANGED
@@ -10,7 +10,8 @@ export interface CallerIdentity {
10
10
  tenantId: string | null;
11
11
  userId: string | null;
12
12
  scopes: string[];
13
- connectorId?: string;
13
+ peerRole?: 'primary' | 'replica';
14
+ peerId?: string;
14
15
  source: 'session' | 'key' | 'none';
15
16
  }
16
17
  export declare function resolveCaller(keys: KeyStore): MiddlewareHandler;
package/dist/caller.js CHANGED
@@ -37,7 +37,8 @@ export function resolveCaller(keys) {
37
37
  tenantId: row.tenantId,
38
38
  userId: row.ownerUserId,
39
39
  scopes: [...row.scopes],
40
- connectorId: row.connectorId,
40
+ peerRole: row.peerRole,
41
+ peerId: row.peerId,
41
42
  source: 'key',
42
43
  });
43
44
  return next();
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Conflict bucket helpers. Conflicts stored at
3
+ * {dataDir}/docs/<tenant>/__sync__/conflicts/<shardId>/<path>.conflict.json
4
+ *
5
+ * Shape: ConflictFile { path, shardId, branches[] }.
6
+ */
7
+ import type { ConflictBranch, ConflictFile } from 'sh3-core';
8
+ export interface ConflictRef {
9
+ shardId: string;
10
+ path: string;
11
+ }
12
+ export declare class ConflictBucket {
13
+ #private;
14
+ constructor(dataDir: string);
15
+ append(tenant: string, shardId: string, path: string, branch: ConflictBranch): Promise<void>;
16
+ read(tenant: string, shardId: string, path: string): Promise<ConflictFile | null>;
17
+ clear(tenant: string, shardId: string, path: string): Promise<void>;
18
+ list(tenant: string): Promise<ConflictRef[]>;
19
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Conflict bucket helpers. Conflicts stored at
3
+ * {dataDir}/docs/<tenant>/__sync__/conflicts/<shardId>/<path>.conflict.json
4
+ *
5
+ * Shape: ConflictFile { path, shardId, branches[] }.
6
+ */
7
+ import { readFile, writeFile, mkdir, rm, readdir } from 'node:fs/promises';
8
+ import { dirname, join } from 'node:path';
9
+ export class ConflictBucket {
10
+ #dataDir;
11
+ constructor(dataDir) {
12
+ this.#dataDir = dataDir;
13
+ }
14
+ async append(tenant, shardId, path, branch) {
15
+ const existing = (await this.read(tenant, shardId, path)) ?? {
16
+ path,
17
+ shardId,
18
+ branches: [],
19
+ };
20
+ existing.branches.push(branch);
21
+ const p = this.#path(tenant, shardId, path);
22
+ await mkdir(dirname(p), { recursive: true });
23
+ await writeFile(p, JSON.stringify(existing, null, 2));
24
+ }
25
+ async read(tenant, shardId, path) {
26
+ try {
27
+ const raw = await readFile(this.#path(tenant, shardId, path), 'utf-8');
28
+ return JSON.parse(raw);
29
+ }
30
+ catch (err) {
31
+ if (err?.code === 'ENOENT')
32
+ return null;
33
+ throw err;
34
+ }
35
+ }
36
+ async clear(tenant, shardId, path) {
37
+ try {
38
+ await rm(this.#path(tenant, shardId, path));
39
+ }
40
+ catch (err) {
41
+ if (err?.code !== 'ENOENT')
42
+ throw err;
43
+ }
44
+ }
45
+ async list(tenant) {
46
+ const root = join(this.#dataDir, 'docs', tenant, '__sync__', 'conflicts');
47
+ const out = [];
48
+ await this.#walk(root, '', async (rel) => {
49
+ if (!rel.endsWith('.conflict.json'))
50
+ return;
51
+ const parts = rel.split(/[/\\]/);
52
+ const shardId = parts[0];
53
+ const filePath = parts.slice(1).join('/').replace(/\.conflict\.json$/, '');
54
+ out.push({ shardId, path: filePath });
55
+ });
56
+ return out;
57
+ }
58
+ async #walk(base, current, visit) {
59
+ let entries;
60
+ try {
61
+ entries = await readdir(join(base, current), { withFileTypes: true });
62
+ }
63
+ catch (err) {
64
+ if (err?.code === 'ENOENT')
65
+ return;
66
+ throw err;
67
+ }
68
+ for (const e of entries) {
69
+ const sub = current ? join(current, e.name) : e.name;
70
+ if (e.isDirectory())
71
+ await this.#walk(base, sub, visit);
72
+ else
73
+ await visit(sub);
74
+ }
75
+ }
76
+ #path(tenant, shardId, path) {
77
+ return join(this.#dataDir, 'docs', tenant, '__sync__', 'conflicts', shardId, `${path}.conflict.json`);
78
+ }
79
+ }
@@ -0,0 +1,11 @@
1
+ export { TenantDocStore } from './store.js';
2
+ export type { ApplyFromPeerInput, ApplyResult, TenantDocStoreDeps } from './store.js';
3
+ export { PolicyCache, resolveSyncMode } from './policy.js';
4
+ export { TickCounter } from './tick.js';
5
+ export { PeerRoles, type PeerRole } from './roles.js';
6
+ export { ConflictBucket, type ConflictRef } from './conflicts.js';
7
+ export { filterReservedMeta, RESERVED_META_KEYS } from './reserved.js';
8
+ export { readMeta, writeMeta, deleteMeta, type DocMetadata } from './meta.js';
9
+ import { TenantDocStore } from './store.js';
10
+ /** Build a TenantDocStore with default dependencies wired to a data dir. */
11
+ export declare function createTenantDocStore(dataDir: string): TenantDocStore;
@@ -0,0 +1,22 @@
1
+ export { TenantDocStore } from './store.js';
2
+ export { PolicyCache, resolveSyncMode } from './policy.js';
3
+ export { TickCounter } from './tick.js';
4
+ export { PeerRoles } from './roles.js';
5
+ export { ConflictBucket } from './conflicts.js';
6
+ export { filterReservedMeta, RESERVED_META_KEYS } from './reserved.js';
7
+ export { readMeta, writeMeta, deleteMeta } from './meta.js';
8
+ import { TenantDocStore } from './store.js';
9
+ import { PolicyCache } from './policy.js';
10
+ import { TickCounter } from './tick.js';
11
+ import { PeerRoles } from './roles.js';
12
+ import { ConflictBucket } from './conflicts.js';
13
+ /** Build a TenantDocStore with default dependencies wired to a data dir. */
14
+ export function createTenantDocStore(dataDir) {
15
+ return new TenantDocStore({
16
+ dataDir,
17
+ policy: new PolicyCache(dataDir),
18
+ tick: new TickCounter(dataDir),
19
+ roles: new PeerRoles(),
20
+ conflicts: new ConflictBucket(dataDir),
21
+ });
22
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Per-document metadata sidecars. Stored at
3
+ * {dataDir}/docs/<tenant>/__meta__/<shardId>/<path>.meta.json
4
+ *
5
+ * The __meta__ shard id is reserved (already blocked at the HTTP layer
6
+ * by the ADR-019 §13 cleanup pass for __-prefixed ids).
7
+ */
8
+ export type DocMetadata = Record<string, unknown>;
9
+ export declare function readMeta(dataDir: string, tenant: string, shardId: string, path: string): Promise<DocMetadata | null>;
10
+ export declare function writeMeta(dataDir: string, tenant: string, shardId: string, path: string, meta: DocMetadata): Promise<void>;
11
+ export declare function deleteMeta(dataDir: string, tenant: string, shardId: string, path: string): Promise<void>;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Per-document metadata sidecars. Stored at
3
+ * {dataDir}/docs/<tenant>/__meta__/<shardId>/<path>.meta.json
4
+ *
5
+ * The __meta__ shard id is reserved (already blocked at the HTTP layer
6
+ * by the ADR-019 §13 cleanup pass for __-prefixed ids).
7
+ */
8
+ import { readFile, writeFile, mkdir, rm } from 'node:fs/promises';
9
+ import { dirname, join } from 'node:path';
10
+ function metaPath(dataDir, tenant, shardId, path) {
11
+ return join(dataDir, 'docs', tenant, '__meta__', shardId, `${path}.meta.json`);
12
+ }
13
+ export async function readMeta(dataDir, tenant, shardId, path) {
14
+ try {
15
+ const raw = await readFile(metaPath(dataDir, tenant, shardId, path), 'utf-8');
16
+ return JSON.parse(raw);
17
+ }
18
+ catch (err) {
19
+ if (err?.code === 'ENOENT')
20
+ return null;
21
+ throw err;
22
+ }
23
+ }
24
+ export async function writeMeta(dataDir, tenant, shardId, path, meta) {
25
+ const p = metaPath(dataDir, tenant, shardId, path);
26
+ await mkdir(dirname(p), { recursive: true });
27
+ await writeFile(p, JSON.stringify(meta, null, 2));
28
+ }
29
+ export async function deleteMeta(dataDir, tenant, shardId, path) {
30
+ try {
31
+ await rm(metaPath(dataDir, tenant, shardId, path));
32
+ }
33
+ catch (err) {
34
+ if (err?.code !== 'ENOENT')
35
+ throw err;
36
+ }
37
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Sync policy — parse, match, cache. Policy lives at
3
+ * {dataDir}/docs/<tenant>/__sync__/policy.json
4
+ *
5
+ * Cache is per-tenant, in-memory. Invalidated on policy.json write
6
+ * by the caller (the write handler).
7
+ */
8
+ import type { SyncPolicy } from 'sh3-core';
9
+ export declare function resolveSyncMode(policy: SyncPolicy, path: string): 'sync' | 'local-only';
10
+ export declare class PolicyCache {
11
+ #private;
12
+ constructor(dataDir: string);
13
+ get(tenant: string): Promise<SyncPolicy>;
14
+ invalidate(tenant: string): void;
15
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Sync policy — parse, match, cache. Policy lives at
3
+ * {dataDir}/docs/<tenant>/__sync__/policy.json
4
+ *
5
+ * Cache is per-tenant, in-memory. Invalidated on policy.json write
6
+ * by the caller (the write handler).
7
+ */
8
+ import { readFile } from 'node:fs/promises';
9
+ import { join } from 'node:path';
10
+ const EMPTY_POLICY = { version: 0, default: 'sync', rules: [] };
11
+ function globToRegex(pattern) {
12
+ let re = '';
13
+ let i = 0;
14
+ while (i < pattern.length) {
15
+ const c = pattern[i];
16
+ if (c === '*' && pattern[i + 1] === '*') {
17
+ re += '.*';
18
+ i += 2;
19
+ // Skip trailing slash after ** to normalize `a/**/b` matches.
20
+ if (pattern[i] === '/')
21
+ i += 1;
22
+ }
23
+ else if (c === '*') {
24
+ re += '[^/]*';
25
+ i += 1;
26
+ }
27
+ else if (c === '?') {
28
+ re += '.';
29
+ i += 1;
30
+ }
31
+ else if ('.+^$(){}[]|\\'.includes(c)) {
32
+ re += '\\' + c;
33
+ i += 1;
34
+ }
35
+ else {
36
+ re += c;
37
+ i += 1;
38
+ }
39
+ }
40
+ return new RegExp('^' + re + '$');
41
+ }
42
+ export function resolveSyncMode(policy, path) {
43
+ for (const rule of policy.rules) {
44
+ if (globToRegex(rule.path).test(path))
45
+ return rule.mode;
46
+ }
47
+ return policy.default;
48
+ }
49
+ export class PolicyCache {
50
+ #dataDir;
51
+ #byTenant = new Map();
52
+ constructor(dataDir) {
53
+ this.#dataDir = dataDir;
54
+ }
55
+ async get(tenant) {
56
+ const cached = this.#byTenant.get(tenant);
57
+ if (cached)
58
+ return cached;
59
+ const policy = await this.#load(tenant);
60
+ this.#byTenant.set(tenant, policy);
61
+ return policy;
62
+ }
63
+ invalidate(tenant) {
64
+ this.#byTenant.delete(tenant);
65
+ }
66
+ async #load(tenant) {
67
+ const p = join(this.#dataDir, 'docs', tenant, '__sync__', 'policy.json');
68
+ try {
69
+ const raw = await readFile(p, 'utf-8');
70
+ const parsed = JSON.parse(raw);
71
+ if (!parsed || typeof parsed !== 'object')
72
+ return EMPTY_POLICY;
73
+ return {
74
+ version: Number(parsed.version) || 0,
75
+ default: parsed.default === 'local-only' ? 'local-only' : 'sync',
76
+ rules: Array.isArray(parsed.rules) ? parsed.rules : [],
77
+ };
78
+ }
79
+ catch (err) {
80
+ if (err?.code === 'ENOENT')
81
+ return { ...EMPTY_POLICY };
82
+ throw err;
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Reserved metadata names owned by sh3-server's write pipeline. Shards
3
+ * cannot set these via Mode A write — attempts are silently filtered
4
+ * out. sh3-server itself populates them during the write.
5
+ */
6
+ export declare const RESERVED_META_KEYS: readonly ["version", "syncMode", "syncState", "lastKnownVersion", "lastSyncedAt", "origin", "deleted"];
7
+ export declare function filterReservedMeta(input: Record<string, unknown> | undefined): Record<string, unknown>;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Reserved metadata names owned by sh3-server's write pipeline. Shards
3
+ * cannot set these via Mode A write — attempts are silently filtered
4
+ * out. sh3-server itself populates them during the write.
5
+ */
6
+ export const RESERVED_META_KEYS = [
7
+ 'version',
8
+ 'syncMode',
9
+ 'syncState',
10
+ 'lastKnownVersion',
11
+ 'lastSyncedAt',
12
+ 'origin',
13
+ 'deleted',
14
+ ];
15
+ const RESERVED_SET = new Set(RESERVED_META_KEYS);
16
+ export function filterReservedMeta(input) {
17
+ if (!input)
18
+ return {};
19
+ const out = {};
20
+ for (const [k, v] of Object.entries(input)) {
21
+ if (RESERVED_SET.has(k))
22
+ continue;
23
+ out[k] = v;
24
+ }
25
+ return out;
26
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Per-tenant peer role, in-memory only. Persisted source of truth is
3
+ * sh3-sync's peer.json (under its per-shard dataDir). sh3-sync calls
4
+ * ctx.setPeerRole() at boot to inform sh3-server. Absence of a role
5
+ * for a tenant defaults to 'primary' behavior.
6
+ */
7
+ export type PeerRole = 'primary' | 'replica';
8
+ export declare class PeerRoles {
9
+ #private;
10
+ get(tenant: string): PeerRole;
11
+ set(tenant: string, role: PeerRole): void;
12
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Per-tenant peer role, in-memory only. Persisted source of truth is
3
+ * sh3-sync's peer.json (under its per-shard dataDir). sh3-sync calls
4
+ * ctx.setPeerRole() at boot to inform sh3-server. Absence of a role
5
+ * for a tenant defaults to 'primary' behavior.
6
+ */
7
+ export class PeerRoles {
8
+ #byTenant = new Map();
9
+ get(tenant) {
10
+ return this.#byTenant.get(tenant) ?? 'primary';
11
+ }
12
+ set(tenant, role) {
13
+ this.#byTenant.set(tenant, role);
14
+ }
15
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * TenantDocStore — single entry point for tenant-document operations.
3
+ *
4
+ * Owns the write pipeline: version bump (primary) or pending (replica),
5
+ * policy-resolved syncMode caching, reserved-metadata filter, tick advance,
6
+ * conflict bucket on Mode B mismatch. Consumed by the docs HTTP router
7
+ * and by the ServerShardContext.documents(tenant) API.
8
+ */
9
+ import type { DocumentMeta, SyncPolicy, ConflictFile } from 'sh3-core';
10
+ import type { PolicyCache } from './policy.js';
11
+ import type { TickCounter } from './tick.js';
12
+ import type { PeerRoles } from './roles.js';
13
+ import type { ConflictBucket, ConflictRef } from './conflicts.js';
14
+ export interface TenantDocStoreDeps {
15
+ dataDir: string;
16
+ policy: PolicyCache;
17
+ tick: TickCounter;
18
+ roles: PeerRoles;
19
+ conflicts: ConflictBucket;
20
+ }
21
+ export interface ApplyFromPeerInput {
22
+ shardId: string;
23
+ path: string;
24
+ content: string | Buffer;
25
+ incomingVersion: number;
26
+ expectedLocalVersion: number;
27
+ origin: string;
28
+ deleted?: boolean;
29
+ metadata?: Record<string, unknown>;
30
+ }
31
+ export type ApplyResult = {
32
+ applied: true;
33
+ version: number;
34
+ } | {
35
+ applied: false;
36
+ reason: 'stale' | 'conflict' | 'conflict-extended';
37
+ };
38
+ export declare class TenantDocStore {
39
+ #private;
40
+ readonly roles: PeerRoles;
41
+ constructor(deps: TenantDocStoreDeps);
42
+ /** Root filesystem directory this store reads/writes under. */
43
+ get dataDir(): string;
44
+ /** Synchronous tenant enumeration for shard-ctx's `tenants()` entry point. */
45
+ listTenantsSync(): string[];
46
+ /**
47
+ * Persist `__sync__/policy.json` for a tenant and refresh the cached
48
+ * `syncMode` on every existing doc so subsequent reads match the new
49
+ * policy without a restart.
50
+ */
51
+ writePolicy(tenant: string, policy: SyncPolicy): Promise<void>;
52
+ read(tenant: string, shardId: string, path: string): Promise<string | null>;
53
+ readMeta(tenant: string, shardId: string, path: string): Promise<Record<string, unknown> | null>;
54
+ exists(tenant: string, shardId: string, path: string): Promise<boolean>;
55
+ list(tenant: string, shardId: string): Promise<DocumentMeta[]>;
56
+ listAll(tenant: string): Promise<(DocumentMeta & {
57
+ shardId: string;
58
+ })[]>;
59
+ getTick(tenant: string): Promise<number>;
60
+ readPolicy(tenant: string): Promise<SyncPolicy | null>;
61
+ invalidatePolicy(tenant: string): void;
62
+ write(tenant: string, shardId: string, path: string, content: string | Buffer, metadata?: Record<string, unknown>): Promise<{
63
+ version: number;
64
+ syncState: 'synced' | 'pending';
65
+ }>;
66
+ delete(tenant: string, shardId: string, path: string): Promise<void>;
67
+ applyFromPeer(tenant: string, input: ApplyFromPeerInput): Promise<ApplyResult>;
68
+ listConflicts(tenant: string): Promise<ConflictRef[]>;
69
+ readConflict(tenant: string, shardId: string, path: string): Promise<ConflictFile | null>;
70
+ resolveConflict(tenant: string, shardId: string, path: string, choice: 'local' | string | Buffer): Promise<void>;
71
+ }