@stolyarovmn/dsh-ui-registry-aggregator 0.4.13

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/lib/client.js ADDED
@@ -0,0 +1,1254 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@stolyarovmn/dsh-ui-registry-aggregator",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ const react = require("react");
8
+ const jsxRuntime = require("react/jsx-runtime");
9
+ const {
10
+ Button, Modal, Switch, Menu, StateDot,
11
+ IconPlusOutlineRegular, IconRefreshOutlineRegular, IconChevronDownOutlineRegular,
12
+ IconCopyOutlineRegular, IconShareOutlineRegular, IconTrashOutlineRegular,
13
+ IconDatabaseOutlineRegular, IconCordisPluginOutlineRegular, IconLinkOutlineRegular,
14
+ IconWarningOutlineRegular, IconSearchOutlineRegular, IconClockOutlineRegular,
15
+ IconDownloadOutlineRegular, IconCheckOutlineRegular, IconChevronUpOutlineRegular,
16
+ IconChevronLeftOutlineRegular, IconChevronRightOutlineRegular, IconChevronsUpDownOutlineRegular,
17
+ IconFlatListOutlineRegular, IconShieldOutlineRegular, IconChecklistOutlineRegular, IconCodeOutlineRegular,
18
+ } = require("@deepseek-ai/dsh-client-ui-primitives");
19
+ const h = jsxRuntime.jsx;
20
+ const hs = jsxRuntime.jsxs;
21
+
22
+ const NS = "registry-aggregator";
23
+ const CHANNEL = "/api";
24
+ const RPC_PREFIX = "plugin-sources";
25
+ const SOURCE_TYPES = ["dsh-plugin-shop", "dshplugin-app", "npm", "github", "custom-json", "corporate"];
26
+ let configForm;
27
+ let connection;
28
+ let remote;
29
+ const connectionResetListeners = new Set();
30
+
31
+ const css = `
32
+ .pm-root{height:100%;overflow:auto;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-page,transparent);font-size:13px;--pm-line:var(--dsw-alias-border-l4);--pm-panel:var(--dsw-alias-bg-layer-1);--pm-muted:var(--dsw-alias-label-tertiary);--pm-accent:var(--dsw-alias-state-business-primary)}
33
+ .pm-shell{width:100%;max-width:1120px;margin:0 auto;padding:10px 0 28px;box-sizing:border-box}.pm-kicker{margin:0 0 3px;color:var(--pm-accent);font:600 10px/14px ui-monospace,SFMono-Regular,Consolas,monospace;letter-spacing:.13em;text-transform:uppercase}.pm-title{margin:0;font-size:18px;line-height:26px;font-weight:650;letter-spacing:-.015em}.pm-lead{max-width:680px;margin:5px 0 18px;color:var(--dsw-alias-label-secondary);line-height:19px}
34
+ .pm-tabs{display:flex;gap:3px;width:max-content;padding:3px;border:1px solid var(--pm-line);border-radius:9px;background:var(--dsw-alias-bg-layer-2);margin-bottom:16px}.pm-tab{height:29px;padding:0 13px;border:0;border-radius:6px;background:transparent;color:var(--pm-muted);font:600 12px/1 inherit;cursor:pointer}.pm-tab[data-active=true]{background:var(--pm-panel);color:var(--dsw-alias-label-primary);box-shadow:0 1px 3px color-mix(in srgb,#000 12%,transparent)}
35
+ .pm-toolbar{display:flex;gap:8px;align-items:center;margin-bottom:12px}.pm-search{flex:1;position:relative}.pm-search input,.pm-input,.pm-select{box-sizing:border-box;height:35px;width:100%;border:1px solid var(--pm-line);border-radius:7px;background:var(--pm-panel);color:var(--dsw-alias-label-primary);font:inherit;outline:none;padding:0 10px}.pm-search input{padding-left:32px}.pm-search svg{position:absolute;left:10px;top:10px;color:var(--pm-muted)}.pm-search input:focus,.pm-input:focus,.pm-select:focus{border-color:var(--pm-accent);box-shadow:0 0 0 2px color-mix(in srgb,var(--pm-accent) 14%,transparent)}
36
+ .pm-btn{height:33px;padding:0 11px;border:1px solid var(--pm-line);border-radius:7px;background:var(--pm-panel);color:var(--dsw-alias-label-secondary);font:600 12px/1 inherit;cursor:pointer;white-space:nowrap}.pm-btn:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3)}.pm-btn-primary{color:#fff;background:var(--pm-accent);border-color:var(--pm-accent)}.pm-btn-danger{color:var(--dsw-alias-state-error, #d44)}.pm-btn:disabled{opacity:.45;cursor:not-allowed}
37
+ .pm-panel{border:1px solid var(--pm-line);border-radius:10px;background:var(--pm-panel);overflow:hidden}.pm-panel-head{display:flex;align-items:center;justify-content:space-between;padding:11px 13px;border-bottom:1px solid var(--pm-line)}.pm-panel-title{font-weight:650}.pm-count{font:500 11px/1 ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--pm-muted)}
38
+ .pm-form{display:grid;grid-template-columns:minmax(0,1fr) minmax(170px,.55fr);gap:10px 12px;align-items:end;padding:13px;border-bottom:1px solid var(--pm-line)}.pm-field{min-width:0}.pm-field label{display:block;margin:0 0 5px;color:var(--pm-muted);font-size:11px}.pm-field-url{grid-column:1/-1}.pm-add{grid-column:2;justify-self:end}.pm-options{display:flex;gap:14px;grid-column:1/-1;color:var(--dsw-alias-label-secondary);font-size:11px}.pm-check{display:flex;align-items:center;gap:6px}.pm-notice{margin:0;padding:10px 13px;border-bottom:1px solid var(--pm-line);color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}.pm-notice-error{color:var(--dsw-alias-state-error,#d44)}
39
+ .pm-source-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;padding:12px 13px}.pm-source{display:flex;flex-direction:column;gap:10px;padding:13px;border:1px solid var(--pm-line);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}.pm-source-main{min-width:0}.pm-source-line{display:flex;align-items:center;gap:8px}.pm-source-name{font-weight:620}.pm-type,.pm-badge,.pm-tag{display:inline-flex;align-items:center;height:18px;padding:0 6px;border:1px solid var(--pm-line);border-radius:99px;color:var(--pm-muted);font:500 10px/1 ui-monospace,SFMono-Regular,Consolas,monospace}.pm-tag{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}.pm-source-meta{margin-top:4px;color:var(--pm-muted);font:11px/16px ui-monospace,SFMono-Regular,Consolas,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pm-source-actions{display:flex;align-items:center;gap:7px;flex-wrap:wrap}.pm-toggle{accent-color:var(--pm-accent)}.pm-source-details{padding-top:9px;border-top:1px solid var(--pm-line)}.pm-source-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.pm-source-buttons{display:flex;align-items:center;gap:6px}.pm-share{color:var(--pm-accent)}.pm-chevron{transition:transform .15s ease}.pm-chevron[data-open=true]{transform:rotate(180deg)}
40
+ .pm-status{width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-label-quaternary,#888);box-shadow:0 0 0 3px color-mix(in srgb,currentColor 10%,transparent)}.pm-status-ok{background:#30a46c}.pm-status-bad{background:var(--dsw-alias-state-error,#d44)}.pm-status-wait{background:#e5a000}.pm-health{color:var(--pm-muted);font-size:11px}
41
+ .pm-results{display:grid;gap:9px}.pm-card{position:relative;padding:13px 14px;border:1px solid var(--pm-line);border-radius:9px;background:var(--pm-panel)}.pm-card:hover{border-color:var(--dsw-alias-border-l3)}.pm-card-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.pm-plugin-name{font-size:14px;font-weight:650;line-height:20px}.pm-link{color:inherit;text-decoration:none}.pm-link:hover{text-decoration:underline}.pm-version{margin-left:7px;color:var(--pm-muted);font:10px ui-monospace,SFMono-Regular,Consolas,monospace}.pm-signal{display:inline-flex;align-items:center;gap:3px;margin-left:7px;color:var(--pm-muted);font:500 10px/1 ui-monospace,SFMono-Regular,Consolas,monospace}.pm-channel{display:inline-flex;align-items:center;height:18px;margin-left:7px;padding:0 6px;border:1px solid var(--pm-line);border-radius:99px;color:var(--dsw-alias-label-secondary);font:600 9px/1 ui-monospace,SFMono-Regular,Consolas,monospace;text-transform:uppercase}.pm-channel-stable{color:#30a46c}.pm-channel-alpha,.pm-channel-beta,.pm-channel-rc,.pm-channel-prerelease{color:#e5a000}.pm-compat{border:1px solid var(--pm-line);border-radius:99px;padding:3px 6px;color:var(--dsw-alias-label-secondary);font:500 9px/1 ui-monospace,SFMono-Regular,Consolas,monospace;background:transparent}.pm-compat-button{cursor:pointer}.pm-compat-button:hover{border-color:var(--dsw-alias-border-l3);color:var(--dsw-alias-label-primary)}.pm-deprecated{color:var(--dsw-alias-state-error,#d44)}.pm-desc{margin:5px 0 3px;color:var(--dsw-alias-label-secondary);line-height:18px}.pm-desc[data-clamped=true]{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.pm-more{border:0;background:transparent;padding:0;color:var(--pm-accent);font:500 11px/16px inherit;cursor:pointer}.pm-card-actions{display:flex;align-items:center;gap:7px;margin-top:10px}.pm-install-button{min-width:76px}.pm-install-status{font-size:11px;color:var(--pm-muted)}.pm-meta{display:flex;flex-wrap:wrap;gap:6px}.pm-tags{display:flex;flex-wrap:wrap;gap:5px;margin-top:7px}.pm-install{display:flex;gap:7px;align-items:center;margin-top:10px}.pm-command{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:7px 9px;border-radius:6px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font:11px ui-monospace,SFMono-Regular,Consolas,monospace}.pm-icon-btn{width:32px;min-width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--pm-line);border-radius:7px;background:var(--pm-panel);color:var(--dsw-alias-label-secondary);cursor:pointer}.pm-icon-btn:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3)}.pm-icon-btn svg{display:block}
42
+ .pm-browse-options{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:0 0 10px;flex-wrap:wrap}.pm-filter-row{display:flex;align-items:center;gap:7px;flex-wrap:wrap}.pm-filter-select{width:auto;min-width:116px;height:30px}.pm-sort{width:auto;min-width:130px;height:30px}.pm-page-size{width:auto;min-width:76px;height:30px}.pm-pagination{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:12px}.pm-page-controls{display:flex;align-items:center;gap:7px}.pm-page-label{color:var(--pm-muted);font-size:11px}.pm-empty{padding:44px 20px;text-align:center;border:1px dashed var(--pm-line);border-radius:10px;color:var(--pm-muted)}.pm-empty strong{display:block;margin-bottom:5px;color:var(--dsw-alias-label-secondary)}.pm-spin{animation:pm-spin .8s linear infinite}@keyframes pm-spin{to{transform:rotate(360deg)}}@media(max-width:720px){.pm-shell{padding:8px 0 28px}.pm-source-list{grid-template-columns:1fr}.pm-form{grid-template-columns:1fr}.pm-field-url,.pm-add,.pm-options{grid-column:1}.pm-add{justify-self:stretch;width:100%}.pm-source{grid-template-columns:1fr}.pm-source-actions{justify-content:flex-start}.pm-card-head{display:block}.pm-card-head>.pm-btn{margin-top:8px}}
43
+
44
+ /* Registry Aggregator source layout */
45
+ .pm-root{--pm-blue:#5792ff;--pm-green:#2dcc74;--pm-danger:#ff5f66;background:radial-gradient(900px 420px at 50% -180px,color-mix(in srgb,var(--pm-blue) 10%,transparent),transparent 72%),var(--dsw-specific-page,transparent)}
46
+ .pm-shell{max-width:1200px;padding:18px 0 34px}
47
+ .pm-kicker{margin-bottom:5px;color:#6da6ff;font-size:10px;letter-spacing:.18em}
48
+ .pm-title{font-size:30px;line-height:38px;font-weight:720;letter-spacing:-.025em}
49
+ .pm-lead{max-width:900px;margin:5px 0 20px;font-size:14px;line-height:21px;color:var(--dsw-alias-label-secondary)}
50
+ .pm-tabs{gap:0;padding:3px;margin-bottom:18px;border-radius:11px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 86%,transparent)}
51
+ .pm-tab{height:38px;min-width:112px;padding:0 17px;border:1px solid transparent;border-radius:8px;font-size:13px}
52
+ .pm-tab[data-active=true]{border-color:color-mix(in srgb,var(--pm-blue) 75%,transparent);background:color-mix(in srgb,var(--pm-blue) 14%,var(--pm-panel));color:#87b3ff;box-shadow:none}
53
+ .pm-tab-content{display:inline-flex;align-items:center;justify-content:center;gap:9px}
54
+ .pm-tab-content svg{width:17px;height:17px}
55
+
56
+ .pm-sources-view{display:grid;gap:16px}
57
+ .pm-add-source-panel,.pm-connected-panel{overflow:hidden;border:1px solid color-mix(in srgb,var(--pm-line) 92%,#6f8ec8 8%);border-radius:13px;background:linear-gradient(180deg,color-mix(in srgb,var(--pm-panel) 96%,#14233c 4%),var(--pm-panel))}
58
+ .pm-section-head{display:flex;align-items:center;justify-content:space-between;gap:18px;padding:18px 20px}
59
+ .pm-section-head-main{display:flex;align-items:center;gap:14px;min-width:0}
60
+ .pm-section-icon{width:44px;height:44px;flex:0 0 44px;display:grid;place-items:center;border:1px solid color-mix(in srgb,var(--pm-blue) 55%,transparent);border-radius:50%;color:#72a8ff;background:color-mix(in srgb,var(--pm-blue) 13%,transparent)}
61
+ .pm-section-icon svg{width:22px;height:22px}
62
+ .pm-section-title{font-size:15px;line-height:20px;font-weight:700;color:var(--dsw-alias-label-primary)}
63
+ .pm-section-lead{margin-top:3px;color:var(--pm-muted);font-size:12px;line-height:17px}
64
+ .pm-disclosure,.pm-refresh-icon{width:34px;height:34px;display:inline-grid;place-items:center;flex:0 0 34px;border:1px solid transparent;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}
65
+ .pm-refresh-icon{border-color:var(--pm-line);background:color-mix(in srgb,var(--pm-panel) 92%,transparent)}
66
+ .pm-disclosure:hover,.pm-refresh-icon:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3)}
67
+ .pm-disclosure svg,.pm-refresh-icon svg{width:17px;height:17px}
68
+
69
+ .pm-form.pm-form-redesign{display:grid;grid-template-columns:minmax(190px,1.05fr) minmax(155px,.65fr) minmax(250px,1.15fr) auto;gap:12px 16px;align-items:end;padding:16px 20px 18px;border-top:1px solid var(--pm-line);border-bottom:0}
70
+ .pm-form-redesign .pm-field-url{grid-column:auto}
71
+ .pm-form-redesign .pm-field label{margin-bottom:6px;font-size:11px;color:var(--dsw-alias-label-secondary)}
72
+ .pm-required{color:var(--pm-danger);margin-left:3px}
73
+ .pm-form-redesign .pm-input,.pm-form-redesign .pm-select{height:38px;border-radius:8px;background:color-mix(in srgb,var(--pm-panel) 92%,#000 8%)}
74
+ .pm-input-wrap{position:relative}
75
+ .pm-input-wrap .pm-input{padding-left:34px}
76
+ .pm-input-leading{position:absolute;left:10px;top:50%;transform:translateY(-50%);display:grid;place-items:center;color:var(--dsw-alias-label-secondary);pointer-events:none}
77
+ .pm-input-leading svg{width:15px;height:15px}
78
+ .pm-form-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px}
79
+ .pm-form-actions .pm-btn{height:38px;border-radius:8px}
80
+ .pm-form-error{grid-column:1 / span 2;justify-self:start;display:inline-flex;align-items:center;gap:8px;margin:0;padding:8px 11px;border:1px solid color-mix(in srgb,var(--pm-danger) 55%,transparent);border-radius:8px;background:color-mix(in srgb,var(--pm-danger) 9%,var(--pm-panel));color:var(--pm-danger);font-size:11px;font-weight:600;box-shadow:0 0 16px color-mix(in srgb,var(--pm-danger) 7%,transparent)}
81
+ .pm-form-error svg{width:14px;height:14px}
82
+ .pm-btn-secondary{background:transparent}
83
+
84
+ .pm-connected-head{border-bottom:1px solid var(--pm-line)}
85
+ .pm-source-list{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;padding:14px 20px 18px}
86
+ .pm-source{gap:0;padding:0;overflow:hidden;border:1px solid color-mix(in srgb,var(--pm-line) 88%,#708bc2 12%);border-radius:12px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 92%,#0b1627 8%)}
87
+ .pm-source-head{align-items:center;padding:14px 15px;gap:14px}
88
+ .pm-source-main{display:flex;align-items:center;gap:11px;min-width:0}
89
+ .pm-source-logo{width:42px;height:42px;flex:0 0 42px;display:grid;place-items:center;overflow:hidden;border-radius:9px;border:1px solid var(--pm-line);font-weight:800}
90
+ .pm-source-logo-npm{border-color:#d3414b;background:#cf3541;color:#fff;font:800 12px/1 ui-monospace,SFMono-Regular,Consolas,monospace}
91
+ .pm-source-logo-github{background:#11151c;color:#fff}
92
+ .pm-source-logo-generic{background:color-mix(in srgb,var(--pm-blue) 9%,var(--pm-panel));color:#9fbcf4}
93
+ .pm-source-logo svg{width:24px;height:24px}
94
+ .pm-source-summary{min-width:0;flex:1}
95
+ .pm-source-line{gap:8px}
96
+ .pm-source-name{font-size:14px;font-weight:700}
97
+ .pm-type{height:21px;padding:0 8px;font-size:10px}
98
+ .pm-source-evidence{display:flex;align-items:center;gap:10px;margin-top:6px;color:var(--pm-muted);font-size:11px;white-space:nowrap}
99
+ .pm-source-evidence-item{display:inline-flex;align-items:center;gap:6px;min-width:0}
100
+ .pm-source-evidence svg{width:14px;height:14px}
101
+ .pm-evidence-separator{width:1px;height:16px;background:var(--pm-line)}
102
+ .pm-status{width:8px;height:8px;box-shadow:0 0 0 3px color-mix(in srgb,currentColor 10%,transparent)}
103
+ .pm-source-buttons{gap:9px;flex:0 0 auto}
104
+ .pm-source-switch{display:inline-flex;align-items:center;gap:8px;color:var(--dsw-alias-label-secondary);font-size:11px;cursor:pointer}
105
+ .pm-source-switch input{position:absolute;opacity:0;pointer-events:none}
106
+ .pm-switch-track{position:relative;width:38px;height:21px;border-radius:99px;background:#5d6675;transition:background .16s ease}
107
+ .pm-switch-track::after{content:"";position:absolute;top:3px;left:3px;width:15px;height:15px;border-radius:50%;background:#f8fbff;transition:transform .16s ease}
108
+ .pm-source-switch input:checked + .pm-switch-track{background:var(--pm-green)}
109
+ .pm-source-switch input:checked + .pm-switch-track::after{transform:translateX(17px)}
110
+ .pm-source-switch input:disabled + .pm-switch-track{opacity:.55}
111
+ .pm-source-disclosure{width:28px;height:28px;display:grid;place-items:center;border:0;border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}
112
+ .pm-source-disclosure:hover{color:var(--dsw-alias-label-primary);background:color-mix(in srgb,var(--pm-panel) 80%,transparent)}
113
+ .pm-source-details{padding:0 15px 14px;border-top:1px solid var(--pm-line)}
114
+ .pm-source-url-label{margin:11px 0 6px;color:var(--dsw-alias-label-secondary);font-size:11px}
115
+ .pm-source-url-wrap{position:relative}
116
+ .pm-source-url{height:35px;width:100%;box-sizing:border-box;padding:0 37px 0 10px;border:1px solid var(--pm-line);border-radius:7px;outline:none;background:color-mix(in srgb,var(--pm-panel) 86%,#000 14%);color:var(--dsw-alias-label-secondary);font:11px ui-monospace,SFMono-Regular,Consolas,monospace}
117
+ .pm-copy-url{position:absolute;right:4px;top:50%;transform:translateY(-50%);width:28px;height:28px;display:grid;place-items:center;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}
118
+ .pm-copy-url:hover{color:var(--dsw-alias-label-primary);background:color-mix(in srgb,var(--pm-panel) 80%,transparent)}
119
+ .pm-source-actions{margin-top:10px;gap:8px}
120
+ .pm-source-action{width:34px;height:34px;display:grid;place-items:center;border:1px solid var(--pm-line);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}
121
+ .pm-source-action:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3)}
122
+ .pm-source-action-danger{color:var(--pm-danger);border-color:color-mix(in srgb,var(--pm-danger) 30%,var(--pm-line))}
123
+ .pm-source-action svg,.pm-copy-url svg{width:15px;height:15px}
124
+ .pm-source-error{margin-top:8px;color:var(--pm-danger);font-size:10px;white-space:normal}
125
+ .pm-connected-notice{margin:0 20px 12px;border-radius:8px;border:1px solid var(--pm-line)}
126
+
127
+ @media(max-width:900px){.pm-form.pm-form-redesign{grid-template-columns:1fr 1fr}.pm-form-redesign .pm-field-url{grid-column:1/-1}.pm-form-actions{grid-column:1/-1}.pm-form-error{grid-column:1/-1}.pm-source-list{grid-template-columns:1fr}}
128
+ @media(max-width:620px){.pm-shell{padding-top:10px}.pm-title{font-size:24px;line-height:30px}.pm-form.pm-form-redesign{grid-template-columns:1fr;padding:14px}.pm-form-redesign .pm-field-url,.pm-form-actions,.pm-form-error{grid-column:1}.pm-form-actions{justify-content:stretch}.pm-form-actions .pm-btn{flex:1}.pm-section-head{padding:15px}.pm-source-list{padding:12px}.pm-source-head{align-items:flex-start}.pm-source-buttons{flex-direction:column;align-items:flex-end}}
129
+ /* Reference UI alignment */
130
+ .pm-shell{max-width:1430px;padding:22px 0 34px}
131
+ .pm-title{font-size:31px;line-height:39px}
132
+ .pm-lead{max-width:1080px;font-size:14px;line-height:21px;margin-bottom:22px}
133
+ .pm-tabs{margin-bottom:18px;border-radius:12px}
134
+ .pm-tab{height:46px;min-width:168px;padding:0 22px;border-radius:9px;font-size:13px}
135
+ .pm-tab-content{gap:11px}
136
+ .pm-tab-content svg{width:19px;height:19px}
137
+ .pm-add-source-panel,.pm-connected-panel{border-radius:13px}
138
+ .pm-section-head{padding:22px 24px}
139
+ .pm-section-head-main{gap:16px}
140
+ .pm-section-icon{width:46px;height:46px;flex-basis:46px}
141
+ .pm-section-icon svg{width:24px;height:24px}
142
+ .pm-section-title{font-size:16px;line-height:22px}
143
+ .pm-section-lead{font-size:12px;line-height:18px}
144
+ .pm-disclosure{width:38px;height:38px}
145
+ .pm-refresh-icon{width:40px;height:40px;flex-basis:40px;border-radius:9px}
146
+ .pm-disclosure svg,.pm-refresh-icon svg{width:19px;height:19px}
147
+ .pm-form.pm-form-redesign{grid-template-columns:minmax(260px,1.25fr) minmax(190px,.75fr) minmax(330px,1.35fr);gap:12px 22px;align-items:end;padding:18px 24px 20px}
148
+ .pm-form-redesign .pm-field-url{grid-column:3}
149
+ .pm-form-redesign .pm-input,.pm-form-redesign .pm-select{height:42px;border-radius:9px}
150
+ .pm-form-redesign .pm-field label{margin-bottom:7px;font-size:11px}
151
+ .pm-form-actions{grid-column:3;grid-row:2;justify-self:end;gap:12px}
152
+ .pm-form-actions .pm-btn{height:40px;border-radius:9px;padding:0 18px;font-size:12px}
153
+ .pm-form-actions .pm-btn-primary{padding-left:20px;padding-right:20px}
154
+ .pm-form-error{grid-column:1;grid-row:2;padding:9px 12px;border-radius:9px}
155
+ .pm-source-list{gap:18px;padding:16px 22px 20px}
156
+ .pm-source{border-radius:12px}
157
+ .pm-source-head{padding:16px 18px}
158
+ .pm-source-main{gap:13px}
159
+ .pm-source-logo{width:46px;height:46px;flex-basis:46px;border-radius:10px}
160
+ .pm-source-logo svg{width:27px;height:27px}
161
+ .pm-source-name{font-size:15px}
162
+ .pm-source-evidence{margin-top:7px;font-size:11px}
163
+ .pm-source-buttons{gap:11px}
164
+ .pm-source-disclosure{width:34px;height:34px}
165
+ .pm-source-disclosure svg{width:17px;height:17px}
166
+ .pm-source-details{padding:0 18px 16px}
167
+ .pm-source-url-label{margin:13px 0 7px}
168
+ .pm-source-url{height:39px;border-radius:8px;padding-right:43px}
169
+ .pm-copy-url{width:32px;height:32px;right:4px}
170
+ .pm-source-actions{margin-top:13px;gap:12px}
171
+ .pm-source-action{width:46px;height:40px;border-radius:9px}
172
+ .pm-source-action svg,.pm-copy-url svg{width:18px;height:18px}
173
+ .pm-source-logo-npm{background:#d83243;border-color:#df4453;color:#fff}
174
+ .pm-source-logo-github{background:#121821;border-color:#384355}
175
+ .pm-source-logo-generic{background:color-mix(in srgb,var(--pm-blue) 13%,var(--pm-panel));border-color:color-mix(in srgb,var(--pm-blue) 38%,var(--pm-line))}
176
+ @media(max-width:1100px){.pm-form.pm-form-redesign{grid-template-columns:1fr 220px}.pm-form-redesign .pm-field-url{grid-column:1/-1}.pm-form-actions{grid-column:1/-1;grid-row:auto}.pm-form-error{grid-column:1/-1;grid-row:auto}}
177
+ @media(max-width:720px){.pm-shell{padding:12px 0 28px}.pm-tab{min-width:138px}.pm-form.pm-form-redesign{grid-template-columns:1fr;padding:14px}.pm-form-redesign .pm-field-url,.pm-form-actions,.pm-form-error{grid-column:1}.pm-form-actions{justify-self:stretch}.pm-form-actions .pm-btn{flex:1}}
178
+
179
+ /* Native DSH 0.1.7 geometry */
180
+ .pm-shell{max-width:960px;padding:12px 0 28px}
181
+ .pm-root{font-size:13px;background:var(--dsw-specific-page,transparent)}
182
+ .pm-title{font-size:20px;line-height:28px;font-weight:500;letter-spacing:0}
183
+ .pm-lead{max-width:760px;margin:4px 0 20px;font-size:13px;line-height:20px}
184
+ .pm-kicker{font-size:10px;line-height:14px;margin-bottom:4px}
185
+
186
+ .pm-tabs{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));width:300px;padding:4px;gap:0;border:0;border-radius:12px;background:var(--dsw-alias-bg-module-platform);margin-bottom:24px}
187
+ .pm-tab{height:34px;min-width:0;padding:0 12px;border:0;border-radius:8px;font-size:14px;line-height:20px;font-weight:400;color:var(--dsw-alias-label-secondary)}
188
+ .pm-tab[data-active=true]{border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font-weight:600;box-shadow:none}
189
+ .pm-tab-content{gap:6px}
190
+ .pm-tab-content svg{width:16px;height:16px}
191
+
192
+ .pm-sources-view{gap:20px}
193
+ .pm-add-source-panel,.pm-connected-panel{border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;background:var(--dsw-alias-bg-layer-1)}
194
+ .pm-section-head{padding:12px 14px;gap:12px}
195
+ .pm-section-head-main{gap:12px}
196
+ .pm-section-icon{width:40px;height:40px;flex:0 0 40px;border:.5px solid var(--dsw-alias-border-l3);border-radius:10px;background:transparent;color:var(--dsw-alias-label-secondary)}
197
+ .pm-section-icon svg{width:18px;height:18px}
198
+ .pm-section-title{font-size:14px;line-height:20px;font-weight:500}
199
+ .pm-section-lead{margin-top:2px;font-size:13px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
200
+ .pm-disclosure,.pm-refresh-icon,.pm-source-disclosure,.pm-source-action,.pm-copy-url{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:0;border-radius:28px;background:transparent;color:var(--dsw-alias-label-caption);cursor:pointer}
201
+ .pm-disclosure:hover:not(:disabled),.pm-refresh-icon:hover:not(:disabled),.pm-source-disclosure:hover:not(:disabled),.pm-source-action:hover:not(:disabled),.pm-copy-url:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}
202
+ .pm-refresh-icon{flex:0 0 28px}
203
+ .pm-disclosure svg,.pm-refresh-icon svg,.pm-source-disclosure svg,.pm-source-action svg,.pm-copy-url svg{width:16px;height:16px;display:block}
204
+ .pm-disclosure[aria-expanded=true] .pm-chevron,.pm-source-disclosure[aria-expanded=true] .pm-chevron{transform:rotate(180deg)}
205
+ .pm-chevron{transition:transform 160ms ease}
206
+
207
+ .pm-form.pm-form-redesign{grid-template-columns:minmax(0,1fr) 190px minmax(0,1.15fr);gap:12px 16px;padding:14px;border-top:.5px solid var(--dsw-alias-border-l2)}
208
+ .pm-form-redesign .pm-field-url{grid-column:3}
209
+ .pm-form-redesign .pm-field label{margin-bottom:6px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
210
+ .pm-form-redesign .pm-input,.pm-form-redesign .pm-select{height:32px;padding:0 8px;border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;background:var(--dsw-alias-bg-layer-1);font-size:14px;line-height:22px}
211
+ .pm-form-redesign .pm-input:focus,.pm-form-redesign .pm-select:focus{border-color:var(--dsw-alias-brand-primary);box-shadow:none}
212
+ .pm-input-wrap .pm-input{padding-left:30px}
213
+ .pm-input-leading{left:8px;color:var(--dsw-alias-label-tertiary)}
214
+ .pm-input-leading svg{width:16px;height:16px}
215
+ .pm-form-actions{grid-column:3;grid-row:2;justify-self:end;gap:8px}
216
+ .pm-form-error{grid-column:1 / span 2;grid-row:2;margin:0;padding:6px 10px;border:0;border-radius:10px;background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);font-size:12px;line-height:18px;font-weight:400;color:var(--dsw-alias-state-error-primary);box-shadow:none}
217
+
218
+ .pm-connected-head{border-bottom:.5px solid var(--dsw-alias-border-l2)}
219
+ .pm-source-list{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;padding:8px 14px 14px}
220
+ .pm-source{border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;background:transparent}
221
+ .pm-source-head{padding:8px;gap:14px}
222
+ .pm-source-main{gap:14px}
223
+ .pm-source-logo{width:48px;height:48px;flex:0 0 48px;border:.5px solid var(--dsw-alias-border-l3);border-radius:10px;background:transparent}
224
+ .pm-source-logo-npm{background:transparent;border-color:var(--dsw-alias-border-l3);color:#cb3837}
225
+ .pm-source-logo-npm svg{width:30px;height:30px}
226
+ .pm-source-logo-github{background:transparent;color:var(--dsw-alias-label-primary)}
227
+ .pm-source-logo-github svg{width:28px;height:28px}
228
+ .pm-source-logo-generic{background:transparent;color:var(--dsw-alias-label-secondary)}
229
+ .pm-source-name{font-size:14px;line-height:20px;font-weight:500}
230
+ .pm-type{height:auto;padding:1px 8px;border:.5px solid var(--dsw-alias-border-l4);font-size:11px;line-height:17px;font-weight:500;color:var(--dsw-alias-label-tertiary)}
231
+ .pm-source-evidence{gap:8px;margin-top:4px;font-size:12.5px;line-height:18px;color:var(--dsw-alias-label-secondary)}
232
+ .pm-source-evidence svg{width:14px;height:14px}
233
+ .pm-evidence-separator{height:16px;background:var(--dsw-alias-border-l2)}
234
+ .pm-source-buttons{gap:8px}
235
+ .pm-source-switch{display:inline-flex;align-items:center;gap:8px;font-size:12.5px;line-height:18px;color:var(--dsw-alias-label-secondary)}
236
+ .pm-source-details{padding:10px 8px 12px;border-top:.5px solid var(--dsw-alias-border-l2)}
237
+ .pm-source-url-label{margin:0 0 6px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
238
+ .pm-source-url{height:32px;padding:0 34px 0 8px;border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;background:var(--dsw-alias-bg-layer-1);font-size:12px;line-height:18px}
239
+ .pm-copy-url{right:2px}
240
+ .pm-source-actions{margin-top:8px;gap:4px}
241
+ .pm-source-action-danger{color:var(--dsw-alias-state-error-primary)}
242
+ .pm-source-error{margin-top:6px;font-size:12px;line-height:18px;color:var(--dsw-alias-state-error-primary)}
243
+
244
+ @media(max-width:900px){.pm-form.pm-form-redesign{grid-template-columns:1fr 180px}.pm-form-redesign .pm-field-url{grid-column:1/-1}.pm-form-actions{grid-column:1/-1;grid-row:auto}.pm-form-error{grid-column:1/-1;grid-row:auto}.pm-source-list{grid-template-columns:1fr}}
245
+ @media(max-width:620px){.pm-shell{padding-top:8px}.pm-tabs{width:100%}.pm-form.pm-form-redesign{grid-template-columns:1fr}.pm-form-redesign .pm-field-url,.pm-form-actions,.pm-form-error{grid-column:1}.pm-form-actions{justify-self:stretch}.pm-section-head{padding:10px 8px}.pm-source-list{padding:8px}}
246
+
247
+
248
+ .pm-section-head-main{gap:0}
249
+ .pm-section-icon{display:none}
250
+ .pm-source-line{gap:0}
251
+ .pm-source-evidence-item>svg{flex:0 0 14px;width:14px;height:14px}
252
+
253
+
254
+ /* Native Browse alignment */
255
+ .pm-browse-view{display:grid;gap:12px}
256
+ .pm-browse-toolbar{display:flex;align-items:center;gap:8px;margin:0}
257
+ .pm-browse-toolbar .pm-search{flex:1}
258
+ .pm-browse-toolbar .pm-search input{height:32px;padding:0 10px 0 32px;border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;background:var(--dsw-alias-bg-layer-1);font-size:14px;line-height:22px}
259
+ .pm-browse-toolbar .pm-search svg{left:10px;top:8px;width:16px;height:16px}
260
+ .pm-browse-refresh{flex:0 0 28px}
261
+ .pm-browse-state{border:.5px solid var(--dsw-alias-border-l3);border-radius:10px;padding:8px 10px;background:transparent;font-size:12px;line-height:18px}
262
+ .pm-browse-content{display:grid;gap:10px}
263
+ .pm-browse-options{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0;flex-wrap:wrap}
264
+ .pm-count{margin:0;color:var(--dsw-alias-label-tertiary);font:500 11px/18px ui-monospace,SFMono-Regular,Consolas,monospace}
265
+ .pm-filter-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
266
+ .pm-stable-filter{display:inline-flex;align-items:center;gap:7px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
267
+ .pm-filter-select,.pm-sort,.pm-page-size{height:32px;border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;background:var(--dsw-alias-bg-layer-1);font-size:13px;line-height:20px;padding:0 28px 0 8px}
268
+ .pm-filter-select{min-width:120px}
269
+ .pm-sort{min-width:155px}
270
+ .pm-page-size{min-width:72px}
271
+ .pm-results{display:grid;gap:8px}
272
+ .pm-card{position:relative;padding:12px 14px;border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;background:transparent}
273
+ .pm-card:hover{border-color:var(--dsw-alias-border-l4)}
274
+ .pm-card-top{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
275
+ .pm-card-main{min-width:0}
276
+ .pm-card-titleline{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
277
+ .pm-plugin-name{font-size:14px;line-height:20px;font-weight:500}
278
+ .pm-version{margin:0;color:var(--dsw-alias-label-tertiary);font:500 10px/18px ui-monospace,SFMono-Regular,Consolas,monospace}
279
+ .pm-channel{height:18px;margin:0;padding:0 7px;border:.5px solid var(--dsw-alias-border-l4);border-radius:99px;font:600 9px/18px ui-monospace,SFMono-Regular,Consolas,monospace}
280
+ .pm-card-stats{display:flex;align-items:center;gap:10px;margin-top:3px;flex-wrap:wrap}
281
+ .pm-signal{display:inline-flex;align-items:center;gap:4px;margin:0;color:var(--dsw-alias-label-tertiary);font:500 10px/16px ui-monospace,SFMono-Regular,Consolas,monospace}
282
+ .pm-signal svg{width:13px;height:13px}
283
+ .pm-card-description{margin-top:7px}
284
+ .pm-desc{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:18px}
285
+ .pm-more{margin-top:1px;font:500 11px/16px inherit;color:var(--dsw-alias-brand-primary)}
286
+ .pm-card-meta{display:flex;align-items:center;gap:5px;flex-wrap:wrap;margin-top:7px}
287
+ .pm-badge,.pm-tag,.pm-compat{height:18px;padding:0 6px;border:.5px solid var(--dsw-alias-border-l4);border-radius:99px;font:500 10px/17px ui-monospace,SFMono-Regular,Consolas,monospace}
288
+ .pm-repo-link{display:block;max-width:100%;margin-top:6px;color:var(--dsw-alias-label-tertiary);font:11px/17px ui-monospace,SFMono-Regular,Consolas,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
289
+ .pm-install-row{display:grid;grid-template-columns:minmax(0,1fr) 28px auto auto;gap:8px;align-items:center;margin-top:9px}
290
+ .pm-command{height:32px;box-sizing:border-box;display:flex;align-items:center;padding:0 9px;border-radius:8px;background:var(--dsw-alias-bg-layer-2);font:11px/18px ui-monospace,SFMono-Regular,Consolas,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
291
+ .pm-install-row .pm-icon-btn{width:28px;min-width:28px;height:28px;border:0;border-radius:28px;background:transparent}
292
+ .pm-install-row .pm-icon-btn:hover{background:var(--dsw-alias-interactive-bg-hover);border:0}
293
+ .pm-install-status{margin-top:6px;font-size:11px;line-height:17px;color:var(--dsw-alias-state-error-primary)}
294
+ .pm-pagination{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:2px}
295
+ .pm-page-label{font-size:11px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
296
+ .pm-page-controls{display:flex;align-items:center;gap:8px}
297
+
298
+ @media(max-width:820px){
299
+ .pm-browse-options{align-items:flex-start}
300
+ .pm-filter-row{width:100%}
301
+ .pm-filter-select,.pm-sort{flex:1 1 150px}
302
+ .pm-page-size{flex:0 0 72px}
303
+ .pm-install-row{grid-template-columns:minmax(0,1fr) 28px}
304
+ .pm-install-row>button:not(.pm-icon-btn){grid-column:auto}
305
+ }
306
+
307
+
308
+ /* Registry Aggregator Browse v2 */
309
+ .pm-browse-topline,.pm-browse-bottomline{display:flex;align-items:center;justify-content:space-between;gap:12px}
310
+ .pm-browse-bottomline{justify-content:flex-end;margin-top:2px}
311
+ .pm-browse-options{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}
312
+ .pm-filter-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
313
+ .pm-sort-row{display:inline-flex;align-items:center;gap:4px;padding:3px;border:.5px solid var(--dsw-alias-border-l3);border-radius:9px;background:var(--dsw-alias-bg-module-platform)}
314
+ .pm-sort-criterion{position:relative;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;min-width:42px;height:28px;padding:0 8px;border:.5px solid transparent;border-radius:7px;background:transparent;color:var(--dsw-alias-label-tertiary);font:600 12px/1 inherit;cursor:pointer}
315
+ .pm-sort-criterion:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}
316
+ .pm-sort-criterion[data-active=true]{border-color:var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary)}
317
+ .pm-sort-symbol{display:inline-flex;align-items:center;justify-content:center;min-width:14px;height:14px}
318
+ .pm-sort-symbol svg,.pm-sort-direction svg{display:block;width:13px;height:13px}
319
+ .pm-sort-direction{display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-caption)}
320
+ .pm-sort-criterion[data-active=true] .pm-sort-direction{color:var(--dsw-alias-brand-primary)}
321
+ .pm-sort-priority{position:absolute;top:-5px;right:-4px;display:flex;align-items:center;justify-content:center;width:13px;height:13px;border-radius:99px;background:var(--dsw-alias-brand-primary);color:#fff;font:700 8px/13px ui-monospace,SFMono-Regular,Consolas,monospace}
322
+
323
+ .pm-pagination{display:flex;align-items:center;gap:8px;margin:0}
324
+ .pm-page-controls{display:flex;align-items:center;gap:2px}
325
+ .pm-page-button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-width:26px;height:26px;padding:0 6px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);font:500 11px/1 ui-monospace,SFMono-Regular,Consolas,monospace;cursor:pointer}
326
+ .pm-page-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
327
+ .pm-page-button[data-current=true]{background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font-weight:700}
328
+ .pm-page-button:disabled{opacity:.35;cursor:default}
329
+ .pm-page-ellipsis{display:inline-flex;align-items:center;justify-content:center;width:20px;color:var(--dsw-alias-label-caption);font:500 11px/1 ui-monospace,SFMono-Regular,Consolas,monospace}
330
+ .pm-page-size-control{display:inline-flex;align-items:center;gap:3px;height:28px;padding:0 5px 0 7px;border:.5px solid var(--dsw-alias-border-l3);border-radius:7px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-tertiary)}
331
+ .pm-page-size-control select{height:24px;min-width:42px;padding:0 16px 0 2px;border:0;outline:0;background:transparent;color:var(--dsw-alias-label-secondary);font:500 11px/1 ui-monospace,SFMono-Regular,Consolas,monospace}
332
+
333
+ .pm-card{padding:11px 12px}
334
+ .pm-card-top{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
335
+ .pm-card-install{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;flex:0 0 30px;width:30px;height:30px;padding:0;border:.5px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);cursor:pointer}
336
+ .pm-card-install:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-brand-primary)}
337
+ .pm-card-install[data-state=installed]{color:var(--dsw-alias-state-success-primary)}
338
+ .pm-card-install[data-state=failed]{color:var(--dsw-alias-state-error-primary)}
339
+ .pm-card-install:disabled{opacity:.55;cursor:default}
340
+ .pm-card-install svg{display:block;width:16px;height:16px}
341
+
342
+ .pm-card-description{position:relative;margin-top:7px;min-width:0}
343
+ .pm-desc{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:18px}
344
+ .pm-desc[data-clamped=true]{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;padding-right:34px}
345
+ .pm-desc-more{position:absolute;right:0;bottom:0;height:18px;padding:0 1px;border:0;border-radius:0;background:transparent;color:var(--dsw-alias-brand-primary);font:900 15px/18px ui-sans-serif,system-ui,sans-serif;letter-spacing:.08em;cursor:pointer;box-shadow:none;appearance:none}
346
+ .pm-desc-more:hover,.pm-desc-more:focus-visible{background:transparent;color:var(--dsw-alias-brand-primary);filter:brightness(1.12);outline:none;text-decoration:none}
347
+ .pm-desc-less{display:inline-flex;margin-top:2px;padding:0;border:0;background:transparent;color:var(--dsw-alias-brand-primary);font:600 11px/16px inherit;cursor:pointer}
348
+ .pm-card-description[data-expanded=true] .pm-desc{padding-right:0}
349
+
350
+ .pm-command-row{display:grid;grid-template-columns:minmax(0,1fr) 28px;gap:6px;align-items:center;margin-top:8px}
351
+ .pm-command-row .pm-icon-btn{width:28px;min-width:28px;height:28px;border:0;border-radius:28px;background:transparent}
352
+ .pm-command-row .pm-icon-btn:hover{background:var(--dsw-alias-interactive-bg-hover);border:0}
353
+ .pm-sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}
354
+
355
+ @media(max-width:960px){
356
+ .pm-browse-options{align-items:flex-start}
357
+ .pm-sort-row{order:2}
358
+ .pm-pagination{max-width:100%;overflow-x:auto}
359
+ }
360
+ @media(max-width:640px){
361
+ .pm-browse-topline{align-items:flex-start;flex-direction:column}
362
+ .pm-browse-topline .pm-pagination{align-self:flex-end}
363
+ .pm-sort-row{width:100%;justify-content:flex-end}
364
+ }
365
+
366
+
367
+ /* Compact one-line Browse controls */
368
+ .pm-controls-row{display:flex;align-items:center;gap:6px;flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;padding:1px 0 3px;scrollbar-width:none}
369
+ .pm-controls-row::-webkit-scrollbar{display:none}
370
+ .pm-filter-toggle,.pm-compact-filter,.pm-sort-row{flex:0 0 auto}
371
+ .pm-filter-toggle{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:.5px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-tertiary);cursor:pointer}
372
+ .pm-filter-toggle:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}
373
+ .pm-filter-toggle[data-active=true]{background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-border-l4)}
374
+ .pm-compact-filter{box-sizing:border-box;display:inline-flex;align-items:center;gap:4px;height:30px;padding:0 4px 0 7px;border:.5px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-tertiary)}
375
+ .pm-compact-filter[data-active=true]{background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-brand-primary)}
376
+ .pm-compact-filter-icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 14px}
377
+ .pm-compact-filter-icon svg{display:block;width:14px;height:14px}
378
+ .pm-compact-filter select{height:27px;max-width:128px;padding:0 18px 0 2px;border:0;outline:0;background:transparent;color:var(--dsw-alias-label-secondary);font:500 11px/1 inherit}
379
+ .pm-sort-row{gap:3px;padding:2px;border:.5px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-module-platform)}
380
+ .pm-sort-criterion{min-width:36px;height:26px;padding:0 6px;border:.5px solid transparent;border-radius:6px}
381
+ .pm-sort-criterion[data-active=true]{border-color:var(--dsw-alias-border-l4);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 12%,var(--dsw-alias-bg-layer-3));color:var(--dsw-alias-label-primary)}
382
+ .pm-sort-criterion[data-active=true] .pm-sort-direction{color:var(--dsw-alias-brand-primary)}
383
+ .pm-sort-priority{display:none!important}
384
+
385
+ .pm-page-size-control{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;min-width:48px;height:28px;padding:0 7px;border:.5px solid var(--dsw-alias-border-l3);border-radius:7px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font:500 11px/1 ui-monospace,SFMono-Regular,Consolas,monospace;cursor:pointer}
386
+ .pm-page-size-control:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
387
+ .pm-page-size-control:disabled{opacity:.5;cursor:default}
388
+ .pm-page-button[data-current=true]{border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
389
+
390
+ @media(max-width:960px){
391
+ .pm-controls-row{width:100%}
392
+ .pm-sort-row{order:initial}
393
+ }
394
+
395
+
396
+ /* Responsive request feedback, native page-size menu trigger, and source actions */
397
+ .pm-loading-strip{position:relative;display:grid;grid-template-columns:16px auto 1fr;align-items:center;gap:6px;min-height:20px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
398
+ .pm-loading-spinner{display:inline-flex;align-items:center;justify-content:center}
399
+ .pm-loading-track{position:relative;height:2px;overflow:hidden;border-radius:99px;background:var(--dsw-alias-border-l2)}
400
+ .pm-loading-bar{position:absolute;top:0;bottom:0;width:28%;border-radius:99px;background:var(--dsw-alias-brand-primary);animation:pm-loading-slide 1s ease-in-out infinite}
401
+ @keyframes pm-loading-slide{0%{left:-28%}50%{left:52%}100%{left:100%}}
402
+ .pm-results[data-updating=true]{opacity:.68;transition:opacity .12s ease}
403
+ .pm-page-size-control svg:last-child{width:11px;height:11px;color:var(--dsw-alias-label-caption)}
404
+ .pm-source-delete{width:28px;height:28px;display:inline-grid;place-items:center;padding:0;border:0;border-radius:7px;background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer}
405
+ .pm-source-delete:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);color:var(--dsw-alias-state-error-primary)}
406
+ .pm-source-delete:disabled{opacity:.4;cursor:default}
407
+ .pm-source-delete svg{width:15px;height:15px}
408
+ .pm-source-actions{display:none!important}
409
+
410
+
411
+ /* Native themed filter menus and install progress */
412
+ .pm-compact-filter{box-sizing:border-box;display:inline-flex;align-items:center;gap:5px;height:30px;max-width:180px;padding:0 7px;border:.5px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font:500 11px/1 inherit;cursor:pointer}
413
+ .pm-compact-filter:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
414
+ .pm-compact-filter[data-active=true]{background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-brand-primary)}
415
+ .pm-compact-filter-icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 14px}
416
+ .pm-compact-filter-icon svg{display:block;width:14px;height:14px}
417
+ .pm-compact-filter-label{min-width:0;max-width:132px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
418
+ .pm-compact-filter>svg:last-child{flex:0 0 12px;width:12px;height:12px;color:var(--dsw-alias-label-caption)}
419
+ .pm-compact-filter select{display:none!important}
420
+ .pm-star-icon{color:var(--dsw-alias-state-warning-primary,#e5a000);font-weight:800}
421
+ .pm-card-install[data-state=installing]{color:var(--dsw-alias-label-secondary);cursor:progress;opacity:1}
422
+ .pm-card-install[data-state=installing] svg[data-state=ongoing]{width:16px;height:16px}
423
+
424
+
425
+ /* Verified bundle discovery and compact source state */
426
+ .pm-status{flex:0 0 8px;min-width:8px;max-width:8px}
427
+ .pm-source-evidence-item{flex:0 0 auto}
428
+ .pm-source-summary{overflow:hidden}
429
+ .pm-source-switch{gap:0}
430
+ .pm-bundle-state{display:inline-flex;align-items:center;height:18px;padding:0 6px;border:1px solid var(--pm-line);border-radius:99px;color:var(--pm-muted);font:600 10px/1 ui-monospace,SFMono-Regular,Consolas,monospace}
431
+ .pm-bundle-state-bundle{border-color:color-mix(in srgb,var(--dsw-alias-state-success-primary,#30a46c) 45%,var(--pm-line));color:var(--dsw-alias-state-success-primary,#30a46c)}
432
+ .pm-bundle-state-not-bundle{color:var(--dsw-alias-label-tertiary)}
433
+ .pm-bundle-state-unknown{color:var(--dsw-alias-state-warning-primary,#e5a000)}
434
+
435
+
436
+ /* Source-card disclosure geometry and status glow */
437
+ .pm-source-list{align-items:start}
438
+ .pm-source{align-self:start}
439
+ .pm-status{margin-left:2px;box-shadow:none!important;overflow:visible}
440
+ .pm-status-ok{color:#30a46c;background:#30a46c;box-shadow:0 0 7px 1px color-mix(in srgb,#30a46c 58%,transparent)!important}
441
+ .pm-status-bad,.pm-status-wait,.pm-status-off{box-shadow:none!important}
442
+ .pm-source[data-open=false] .pm-source-head{border-bottom:0}
443
+ .pm-chevron[data-open=true]{transform:rotate(180deg)}
444
+
445
+ `;
446
+
447
+ function injectStyle() {
448
+ const key = "plugin-sources";
449
+ const document = window.document;
450
+ if (document.querySelector(`style[data-plugin-css="${key}"]`)) return () => {};
451
+ const style = document.createElement("style");
452
+ style.dataset.pluginCss = key;
453
+ style.textContent = css;
454
+ document.head.appendChild(style);
455
+ return () => style.remove?.();
456
+ }
457
+
458
+ const en = {
459
+ nav: "Registry Aggregator", kicker: "Federated plugin registry", title: "Registry Aggregator", lead: "Aggregate connected plugin registries and catalogs, search them as one index, and install through the native DSH Plugin Manager.",
460
+ "tab.sources": "Sources", "tab.browse": "Browse", "sources.title": "Connected sources", "sources.add": "Add source", "sources.addTitle": "Add a new source", "sources.addLead": "Connect a package registry to discover and install plugins.", "sources.manage": "Manage your connected registries and discover available plugins.", "sources.empty": "No plugin sources connected", "sources.emptyHint": "Add a catalog, npm registry, or GitHub source to begin.",
461
+ "field.name": "Name", "field.type": "Type", "field.url": "Catalog / API URL", "field.urlHint": "Optional for npm and GitHub", "source.remove": "Remove", "source.share": "Share", "source.shared": "Copied", "source.enabled": "Enabled", "source.disabled": "Disabled", "source.checking": "Checking", "source.online": "Status: online", "source.packages": "{count} packages", "source.repositories": "{count} repositories", "source.ok": "Healthy", "source.error": "Unavailable", "source.duplicate": "A source with this id already exists.", "source.required": "Name is required.", "source.urlRequired": "This source type requires a catalog URL.",
462
+ "activation.title": "Registry Aggregator ready", "activation.body": "npm and GitHub sources are connected by default. Open Registry Aggregator to search them together or add more sources.", "activation.open": "Open Registry Aggregator", "activation.later": "Later",
463
+ "browse.search": "Search plugins, packages, repositories…", "browse.refresh": "Refresh", "browse.pageSize": "Per page", "browse.page": "Page {page} of {pages}", "browse.prev": "Previous", "browse.next": "Next", "browse.freshness": "Freshness", "browse.freshness.any": "Any age", "browse.freshness.30": "≤ 30 days", "browse.freshness.90": "≤ 90 days", "browse.freshness.365": "≤ 1 year", "browse.category": "Category", "browse.category.any": "All categories", "browse.dshMetadata": "DSH metadata", "browse.dshMetadata.any": "Any DSH metadata", "browse.dshMetadata.declared": "DSH declared", "browse.dshMetadata.unknown": "DSH unknown", "install": "Install", "install.confirm": "Install?", "installing": "Installing…", "install.checking": "Checking package…", "install.starting": "Starting install…", "install.running": "Installing package…", "install.runningAttempt": "Installing {index}/{total}…", "install.applying": "Applying plugin…", "installed": "Installed", "install.failed": "Install failed", "install.retry": "Retry install", "install.notBundle": "Not a DSH bundle", "install.approvalRequired": "Build-script approval required in the native Plugin Manager", "install.cancel": "Cancel", "description.more": "…", "description.expand": "Expand description", "description.less": "Less", "browse.empty": "No plugins found", "browse.sort": "Sort", "browse.sort.relevance": "Relevance", "browse.sort.stars": "Stars", "browse.sort.downloads": "Downloads", "browse.sort.freshness": "Freshest release", "browse.sort.starsDownloads": "Stars + downloads", "browse.sort.downloadsFreshness": "Downloads + freshness", "browse.sort.name": "Name", "browse.sort.priority": "Sort priority {priority}", "browse.sort.off": "Not active", "browse.sort.asc": "Ascending", "browse.sort.desc": "Descending", "browse.stableOnly": "Stable only", "browse.emptyHint": "No plugins match the current search or filters.", "browse.noSources": "No enabled registry sources. Open Sources and enable or add one.", "browse.sourcesUnavailable": "All enabled registry sources are unavailable.", "browse.loading": "Loading catalogs…", "browse.updating": "Updating results…", "browse.error": "Catalog request failed", "browse.results": "{count} results", "compat.check": "Check DSH compatibility", "compat.none": "DSH not declared", "compat.loading": "DSH …", "compat.apiPeers": "DSH API peers", "released": "released {age}", "repoUpdated": "repo {age}", "deprecated": "deprecated", "copy": "Copy command", "copied": "Copied", "copy.failed": "Could not copy", "install.warning": "Review the package and source before running this third-party install command.", "readonly": "Settings are read-only.", "unavailable": "Plugin source settings are unavailable.",
464
+ };
465
+ const zh = {
466
+ nav: "注册源聚合器", kicker: "联合插件注册表", title: "注册源聚合器", lead: "聚合已连接的插件注册表和目录,统一搜索,并通过原生 DSH Plugin Manager 安装。",
467
+ "tab.sources": "来源", "tab.browse": "浏览", "sources.title": "已连接来源", "sources.add": "添加来源", "sources.addTitle": "添加新来源", "sources.addLead": "连接软件包注册表以发现和安装插件。", "sources.manage": "管理已连接的注册表并发现可用插件。", "sources.empty": "尚未连接插件源", "sources.emptyHint": "添加目录、npm 注册表或 GitHub 来源以开始。",
468
+ "field.name": "名称", "field.type": "类型", "field.url": "目录 / API URL", "field.urlHint": "npm 和 GitHub 可选", "source.remove": "移除", "source.share": "分享", "source.shared": "已复制", "source.enabled": "已启用", "source.disabled": "已禁用", "source.checking": "检查中", "source.online": "状态:在线", "source.packages": "{count} 个软件包", "source.repositories": "{count} 个仓库", "source.ok": "正常", "source.error": "不可用", "source.duplicate": "具有此 ID 的来源已存在。", "source.required": "名称为必填项。", "source.urlRequired": "此来源类型需要目录 URL。",
469
+ "activation.title": "注册源聚合器已就绪", "activation.body": "npm 和 GitHub 来源默认已连接。打开注册源聚合器即可统一搜索或添加更多来源。", "activation.open": "打开注册源聚合器", "activation.later": "稍后",
470
+ "browse.search": "搜索插件、包、仓库…", "browse.refresh": "刷新", "browse.pageSize": "每页", "browse.page": "第 {page} / {pages} 页", "browse.prev": "上一页", "browse.next": "下一页", "browse.freshness": "新鲜度", "browse.freshness.any": "不限时间", "browse.freshness.30": "≤ 30 天", "browse.freshness.90": "≤ 90 天", "browse.freshness.365": "≤ 1 年", "browse.category": "类别", "browse.category.any": "全部类别", "browse.dshMetadata": "DSH 元数据", "browse.dshMetadata.any": "任意 DSH 元数据", "browse.dshMetadata.declared": "已声明 DSH", "browse.dshMetadata.unknown": "DSH 未知", "install": "安装", "install.confirm": "确认安装?", "installing": "安装中…", "installed": "已安装", "install.failed": "安装失败", "install.retry": "重试安装", "install.cancel": "取消", "description.more": "…", "description.expand": "展开描述", "description.less": "收起", "browse.empty": "未找到插件", "browse.sort": "排序", "browse.sort.relevance": "相关性", "browse.sort.stars": "星标", "browse.sort.downloads": "下载量", "browse.sort.freshness": "最新发布", "browse.sort.starsDownloads": "星标 + 下载量", "browse.sort.downloadsFreshness": "下载量 + 新鲜度", "browse.sort.name": "名称", "browse.sort.priority": "排序优先级 {priority}", "browse.sort.off": "未启用", "browse.sort.asc": "升序", "browse.sort.desc": "降序", "browse.stableOnly": "仅稳定版", "browse.emptyHint": "当前搜索或筛选条件没有匹配的插件。", "browse.noSources": "没有启用的注册源。请打开“来源”并启用或添加一个来源。", "browse.sourcesUnavailable": "所有已启用的注册源当前都不可用。", "browse.loading": "正在加载目录…", "browse.updating": "正在更新结果…", "browse.error": "目录请求失败", "browse.results": "{count} 个结果", "compat.check": "检查 DSH 兼容性", "compat.none": "未声明 DSH", "compat.loading": "DSH …", "compat.apiPeers": "DSH API 依赖", "released": "发布 {age}", "repoUpdated": "仓库 {age}", "deprecated": "已弃用", "copy": "复制命令", "copied": "已复制", "copy.failed": "复制失败", "install.warning": "运行此第三方安装命令前,请检查包和来源。", "readonly": "设置为只读。", "unavailable": "插件源设置不可用。",
471
+ };
472
+
473
+ function slug(value) {
474
+ const input = value.trim();
475
+ const ascii = input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 48);
476
+ if (ascii) return ascii;
477
+ let hash = 2166136261;
478
+ for (const char of input) { hash ^= char.codePointAt(0); hash = Math.imul(hash, 16777619); }
479
+ return input ? `source-${(hash >>> 0).toString(36)}` : "";
480
+ }
481
+
482
+ function rpc(endpoint, payload, signal) {
483
+ if (!connection?.rpc?.call) return Promise.reject(new Error("Host connection is unavailable"));
484
+ return connection.rpc.call(CHANNEL, `${RPC_PREFIX}/${endpoint}`, payload, signal).then((result) => {
485
+ if (!result?.ok) throw new Error(result?.error?.message || "Host request failed");
486
+ return result.value;
487
+ });
488
+ }
489
+
490
+ function SearchIcon() { return h(IconSearchOutlineRegular, { size: 16 }); }
491
+
492
+ function CopyIcon({ done = false }) {
493
+ return done
494
+ ? h("svg", { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, "aria-hidden": true, children: h("path", { d: "m5 12 4 4L19 6" }) })
495
+ : hs("svg", { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, "aria-hidden": true, children: [h("rect", { x: 9, y: 9, width: 10, height: 10, rx: 2 }), h("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })] });
496
+ }
497
+
498
+
499
+ function RefreshIcon() { return h(IconRefreshOutlineRegular, { size: 16 }); }
500
+ function PlusIcon() { return h(IconPlusOutlineRegular, { size: 16 }); }
501
+ function NetworkIcon() { return h(IconDatabaseOutlineRegular, { size: 18 }); }
502
+ function ShareIcon() { return h(IconShareOutlineRegular, { size: 16 }); }
503
+ function TrashIcon() { return h(IconTrashOutlineRegular, { size: 16 }); }
504
+ function PackageIcon() {
505
+ return h("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.7, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
506
+ h("path", { d: "m12 3 8 4.5v9L12 21l-8-4.5v-9L12 3Z" }),
507
+ h("path", { d: "m4 7.5 8 4.5 8-4.5" }),
508
+ h("path", { d: "M12 12v9" })
509
+ ] });
510
+ }
511
+ function DatabaseIcon() { return h(IconDatabaseOutlineRegular, { size: 16 }); }
512
+ function GridIcon() { return h(IconCordisPluginOutlineRegular, { size: 16 }); }
513
+ function LinkIcon() { return h(IconLinkOutlineRegular, { size: 16 }); }
514
+ function AlertIcon() { return h(IconWarningOutlineRegular, { size: 16 }); }
515
+
516
+ function GitHubMark() {
517
+ return h("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, children: h("path", { d: "M12 2a10 10 0 0 0-3.2 19.5c.5.1.7-.2.7-.5v-1.9c-2.8.6-3.4-1.2-3.4-1.2-.5-1.2-1.1-1.5-1.1-1.5-.9-.6.1-.6.1-.6 1 .1 1.6 1 1.6 1 .9 1.5 2.3 1.1 2.9.8.1-.6.4-1.1.6-1.3-2.2-.3-4.6-1.1-4.6-4.9 0-1.1.4-2 1-2.6-.1-.3-.4-1.3.1-2.6 0 0 .8-.3 2.7 1a9.4 9.4 0 0 1 4.9 0c1.9-1.3 2.7-1 2.7-1 .5 1.3.2 2.3.1 2.6.6.7 1 1.5 1 2.6 0 3.8-2.3 4.6-4.6 4.9.4.3.7 1 .7 1.8V21c0 .3.2.6.7.5A10 10 0 0 0 12 2Z" }) });
518
+ }
519
+ function Empty({ title, hint }) {
520
+ return hs("div", { className: "pm-empty", children: [h("strong", { children: title }), h("span", { children: hint })] });
521
+ }
522
+
523
+ function AddSourceForm({ t, sources, onSave, busy, readOnly }) {
524
+ const [name, setName] = react.useState("");
525
+ const [type, setType] = react.useState("dshplugin-app");
526
+ const [url, setUrl] = react.useState("");
527
+ const [error, setError] = react.useState("");
528
+ const [expanded, setExpanded] = react.useState(true);
529
+ const reset = () => { setName(""); setType("dshplugin-app"); setUrl(""); setError(""); };
530
+ const submit = async (event) => {
531
+ event.preventDefault();
532
+ const id = slug(name);
533
+ const needsUrl = !["npm", "github"].includes(type);
534
+ if (!id) return setError(t("source.required"));
535
+ if (needsUrl && !url.trim()) return setError(t("source.urlRequired"));
536
+ if (sources.some((source) => source.id === id)) return setError(t("source.duplicate"));
537
+ setError("");
538
+ try {
539
+ const saved = await onSave([...sources, { id, name: name.trim(), type, ...(url.trim() ? { url: url.trim() } : {}), enabled: true }]);
540
+ if (saved === false) return;
541
+ reset();
542
+ } catch (failure) {
543
+ setError(String(failure?.message ?? failure));
544
+ }
545
+ };
546
+ return hs("section", { className: "pm-add-source-panel", children: [
547
+ hs("div", { className: "pm-section-head", children: [
548
+ hs("div", { className: "pm-section-head-main", children: [
549
+ h("div", { children: [h("div", { className: "pm-section-title", children: t("sources.addTitle") }), h("div", { className: "pm-section-lead", children: t("sources.addLead") })] }),
550
+ ] }),
551
+ h("button", { type: "button", className: "pm-disclosure", onClick: () => setExpanded((value) => !value), "aria-expanded": expanded, "aria-label": expanded ? "Collapse" : "Expand", children: h(ChevronIcon, { open: expanded }) }),
552
+ ] }),
553
+ expanded ? hs("form", { className: "pm-form pm-form-redesign", onSubmit: submit, children: [
554
+ hs("div", { className: "pm-field", children: [
555
+ hs("label", { htmlFor: "pm-name", children: [t("field.name"), h("span", { className: "pm-required", children: "*" })] }),
556
+ h("input", { id: "pm-name", className: "pm-input", value: name, placeholder: "e.g. My Registry", onChange: (event) => { setName(event.target.value); if (error) setError(""); }, disabled: readOnly || busy }),
557
+ ] }),
558
+ hs("div", { className: "pm-field", children: [
559
+ h("label", { htmlFor: "pm-type", children: t("field.type") }),
560
+ h("select", { id: "pm-type", className: "pm-select", value: type, onChange: (event) => { setType(event.target.value); if (error) setError(""); }, disabled: readOnly || busy, children: SOURCE_TYPES.map((item) => h("option", { value: item, children: item }, item)) }),
561
+ ] }),
562
+ hs("div", { className: "pm-field pm-field-url", children: [
563
+ h("label", { htmlFor: "pm-url", children: t("field.url") }),
564
+ hs("div", { className: "pm-input-wrap", children: [h("span", { className: "pm-input-leading", children: h(LinkIcon, {}) }), h("input", { id: "pm-url", className: "pm-input", type: "url", value: url, placeholder: t("field.urlHint"), onChange: (event) => { setUrl(event.target.value); if (error) setError(""); }, disabled: readOnly || busy })] }),
565
+ ] }),
566
+ hs("div", { className: "pm-form-actions", children: [
567
+ h(Button, { variant: "outline", size: "md", type: "button", disabled: readOnly || busy, onClick: reset, children: t("install.cancel") }),
568
+ h(Button, { variant: "primary", size: "md", type: "submit", disabled: readOnly || busy, children: t("sources.add") }),
569
+ ] }),
570
+ error ? hs("p", { className: "pm-form-error", role: "alert", children: [h(AlertIcon, {}), h("span", { children: error })] }) : null,
571
+ ] }) : null,
572
+ ] });
573
+ }
574
+
575
+ function ChevronIcon({ open }) {
576
+ return h(IconChevronDownOutlineRegular, { size: 14, className: "pm-chevron", "data-open": open });
577
+ }
578
+
579
+ function sourceEndpoint(source) {
580
+ if (source.url) return source.url;
581
+ if (source.type === "npm") return "https://registry.npmjs.org";
582
+ if (source.type === "github") return "https://api.github.com";
583
+ return source.id;
584
+ }
585
+
586
+ function NpmMark() {
587
+ return h("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, children:
588
+ h("path", { d: "M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z" })
589
+ });
590
+ }
591
+
592
+ function SourceLogo({ source }) {
593
+ if (source.type === "npm") return h("div", { className: "pm-source-logo pm-source-logo-npm", "aria-hidden": true, children: h(NpmMark, {}) });
594
+ if (source.type === "github") return h("div", { className: "pm-source-logo pm-source-logo-github", "aria-hidden": true, children: h(GitHubMark, {}) });
595
+ return h("div", { className: "pm-source-logo pm-source-logo-generic", "aria-hidden": true, children: h(PackageIcon, {}) });
596
+ }
597
+
598
+ function SourceRow({ source, health, t, busy, readOnly, onToggle, onRemove }) {
599
+ const [open, setOpen] = react.useState(true);
600
+ const [copied, setCopied] = react.useState(false);
601
+ const status = source.enabled === false ? "off" : health?.ok === true ? "ok" : health?.ok === false ? "bad" : "wait";
602
+ const endpoint = sourceEndpoint(source);
603
+ const count = Number.isFinite(health?.count) ? health.count : 0;
604
+ const statusText = status === "off" ? t("source.disabled") : status === "ok" ? t("source.online") : status === "bad" ? t("source.error") : t("source.checking");
605
+ const copyEndpoint = async () => {
606
+ try { await globalThis.navigator?.clipboard?.writeText?.(endpoint); setCopied(true); setTimeout(() => setCopied(false), 1400); } catch {}
607
+ };
608
+ return hs("article", { className: "pm-source", "data-open": open, children: [
609
+ hs("div", { className: "pm-source-head", children: [
610
+ hs("div", { className: "pm-source-main", children: [
611
+ h(SourceLogo, { source }),
612
+ h("div", { className: "pm-source-summary", children: [
613
+ h("div", { className: "pm-source-line", children: h("span", { className: "pm-source-name", children: source.name }) }),
614
+ hs("div", { className: "pm-source-evidence", children: [
615
+ hs("span", { className: "pm-source-evidence-item", children: [h("span", { className: `pm-status pm-status-${status}`, "aria-hidden": true }), h("span", { children: statusText })] }),
616
+ status === "ok" ? h("span", { className: "pm-evidence-separator", "aria-hidden": true }) : null,
617
+ status === "ok" ? hs("span", { className: "pm-source-evidence-item", children: [h(PackageIcon, {}), h("span", { children: t(source.type === "github" ? "source.repositories" : "source.packages", { count }) })] }) : null,
618
+ ] }),
619
+ ] }),
620
+ ] }),
621
+ hs("div", { className: "pm-source-buttons", children: [
622
+ h("span", { className: "pm-source-switch", children:
623
+ h(Switch, { checked: source.enabled !== false, disabled: readOnly || busy, label: source.enabled !== false ? t("source.enabled") : t("source.disabled"), onChange: () => onToggle(source.id) })
624
+ }),
625
+ h("button", { type: "button", className: "pm-source-disclosure", onClick: () => setOpen((value) => !value), "aria-expanded": open, "aria-label": open ? "Collapse source" : "Expand source", children: h(ChevronIcon, { open }) }),
626
+ h("button", { type: "button", className: "pm-source-delete", disabled: readOnly || busy, onClick: () => onRemove(source.id), title: t("source.remove"), "aria-label": t("source.remove"), children: h(TrashIcon, {}) }),
627
+ ] }),
628
+ ] }),
629
+ open ? hs("div", { className: "pm-source-details", children: [
630
+ h("div", { className: "pm-source-url-label", children: t("field.url") }),
631
+ hs("div", { className: "pm-source-url-wrap", children: [
632
+ h("input", { className: "pm-source-url", value: endpoint, readOnly: true, title: endpoint }),
633
+ h("button", { type: "button", className: "pm-copy-url", onClick: copyEndpoint, title: copied ? t("copied") : t("copy"), "aria-label": copied ? t("copied") : t("copy"), children: copied ? h(IconCopyOutlineRegular, { size: 16 }) : h(IconCopyOutlineRegular, { size: 16 }) }),
634
+ ] }),
635
+ health?.error ? h("div", { className: "pm-source-error", title: health.error, children: health.error }) : null,
636
+ ] }) : null,
637
+ ] });
638
+ }
639
+
640
+ function SourcesView({ t, sources, writable, saveSources, busy, healthState, refreshHealth }) {
641
+ const [mutationError, setMutationError] = react.useState("");
642
+ const mutate = async (next) => {
643
+ setMutationError("");
644
+ try { await saveSources(next); return true; }
645
+ catch (failure) { setMutationError(String(failure?.message ?? failure)); return false; }
646
+ };
647
+ const healthMap = Object.fromEntries((healthState.data?.sources ?? []).map((row) => [row.source.id, row.health]));
648
+ return hs("section", { className: "pm-sources-view", children: [
649
+ h(AddSourceForm, { t, sources, onSave: mutate, busy, readOnly: !writable }),
650
+ hs("section", { className: "pm-connected-panel", children: [
651
+ hs("div", { className: "pm-section-head pm-connected-head", children: [
652
+ hs("div", { className: "pm-section-head-main", children: [
653
+ h("div", { children: [h("div", { className: "pm-section-title", children: t("sources.title") }), h("div", { className: "pm-section-lead", children: t("sources.manage") })] }),
654
+ ] }),
655
+ h("button", { type: "button", className: "pm-refresh-icon", onClick: refreshHealth, disabled: healthState.loading, title: t("browse.refresh"), "aria-label": t("browse.refresh"), children: h(RefreshIcon, {}) }),
656
+ ] }),
657
+ !writable ? h("p", { className: "pm-notice pm-connected-notice", children: t("readonly") }) : null,
658
+ mutationError ? h("p", { className: "pm-notice pm-notice-error pm-connected-notice", role: "alert", children: mutationError }) : null,
659
+ healthState.error ? h("p", { className: "pm-notice pm-notice-error pm-connected-notice", role: "alert", children: healthState.error }) : null,
660
+ sources.length === 0 ? h(Empty, { title: t("sources.empty"), hint: t("sources.emptyHint") }) : h("div", { className: "pm-source-list", children: sources.map((source) => h(SourceRow, { source, health: healthMap[source.id], t, busy, readOnly: !writable, onToggle: (id) => { void mutate(sources.map((row) => row.id === id ? { ...row, enabled: row.enabled === false } : row)); }, onRemove: (id) => { void mutate(sources.filter((row) => row.id !== id)); } }, source.id)) }),
661
+ ] }),
662
+ ] });
663
+ }
664
+
665
+ function installCommand(plugin) {
666
+ return plugin.install?.spec ? `dsh plugin add ${plugin.install.spec}` : "";
667
+ }
668
+
669
+ function compactNumber(value) {
670
+ if (typeof value !== "number" || !Number.isFinite(value)) return "";
671
+ return new Intl.NumberFormat(undefined, { notation: "compact", maximumFractionDigits: 1 }).format(value);
672
+ }
673
+
674
+ function repositoryEvidenceKey(value) {
675
+ if (typeof value !== "string" || !value.trim()) return "";
676
+ let raw = value.trim().replace(/^git\+/, "").replace(/^git@github\.com:/i, "https://github.com/");
677
+ try {
678
+ const url = new URL(raw);
679
+ url.hash = "";
680
+ url.search = "";
681
+ return url.toString().replace(/\.git$/i, "").replace(/\/$/, "").toLowerCase();
682
+ } catch {
683
+ return raw.replace(/\.git$/i, "").replace(/\/$/, "").toLowerCase();
684
+ }
685
+ }
686
+
687
+ function relativeAge(value) {
688
+ const timestamp = Date.parse(value || "");
689
+ if (!Number.isFinite(timestamp)) return "";
690
+ const days = Math.max(0, Math.floor((Date.now() - timestamp) / 86_400_000));
691
+ if (days < 1) return "<1d";
692
+ if (days < 30) return `${days}d`;
693
+ if (days < 365) return `${Math.floor(days / 30)}mo`;
694
+ return `${Math.floor(days / 365)}y`;
695
+ }
696
+
697
+ function releaseChannelLabel(channel) {
698
+ return channel === "stable" ? "stable" : channel || "";
699
+ }
700
+
701
+ function npmPackageUrl(packageName) {
702
+ return packageName ? `https://www.npmjs.com/package/${encodeURIComponent(packageName).replace(/%2F/gi, "/")}` : "";
703
+ }
704
+
705
+ function ExternalLink({ href, className, children, title }) {
706
+ return h("a", { href, className, title, target: "_blank", rel: "noopener noreferrer", children });
707
+ }
708
+
709
+ function ClampedDescription({ text, t, onExpand }) {
710
+ const [expanded, setExpanded] = react.useState(false);
711
+ const [overflow, setOverflow] = react.useState(false);
712
+ const ref = react.useRef(null);
713
+ react.useEffect(() => {
714
+ if (expanded) return undefined;
715
+ const node = ref.current;
716
+ if (!node) return undefined;
717
+ const measure = () => setOverflow(node.scrollHeight > node.clientHeight + 1);
718
+ measure();
719
+ if (typeof globalThis.ResizeObserver !== "function") return undefined;
720
+ const observer = new globalThis.ResizeObserver(measure);
721
+ observer.observe(node);
722
+ return () => observer.disconnect();
723
+ }, [text, expanded]);
724
+ const toggle = () => {
725
+ const next = !expanded;
726
+ setExpanded(next);
727
+ if (next) onExpand?.();
728
+ };
729
+ return hs("div", { className: "pm-card-description", "data-expanded": expanded, children: [
730
+ h("p", { ref, className: "pm-desc", "data-clamped": !expanded, children: text }),
731
+ !expanded && overflow ? h("button", { type: "button", className: "pm-desc-more", onClick: toggle, title: t("description.expand"), "aria-label": t("description.expand"), children: "..." }) : null,
732
+ expanded ? h("button", { type: "button", className: "pm-desc-less", onClick: toggle, children: t("description.less") }) : null,
733
+ ] });
734
+ }
735
+
736
+ function sortDirectionIcon(direction) {
737
+ if (direction === "asc") return h(IconChevronUpOutlineRegular, { size: 12 });
738
+ if (direction === "desc") return h(IconChevronDownOutlineRegular, { size: 12 });
739
+ return h(IconChevronsUpDownOutlineRegular, { size: 13 });
740
+ }
741
+
742
+ function SortCriterion({ criterion, icon, label, sorts, onCycle, t }) {
743
+ const index = sorts.findIndex((item) => item.key === criterion);
744
+ const active = index >= 0;
745
+ const direction = active ? sorts[index].direction : "off";
746
+ const title = active
747
+ ? `${label} · ${direction === "asc" ? t("browse.sort.asc") : t("browse.sort.desc")}`
748
+ : `${label} · ${t("browse.sort.off")}`;
749
+ return hs("button", {
750
+ type: "button",
751
+ className: "pm-sort-criterion",
752
+ "data-active": active,
753
+ "data-direction": direction,
754
+ title,
755
+ "aria-label": title,
756
+ onClick: () => onCycle(criterion),
757
+ children: [
758
+ h("span", { className: "pm-sort-symbol", children: icon }),
759
+ h("span", { className: "pm-sort-direction", children: sortDirectionIcon(direction) }),
760
+ ],
761
+ });
762
+ }
763
+
764
+ function pageTokens(page, pageCount) {
765
+ if (pageCount <= 7) return Array.from({ length: pageCount }, (_, index) => index + 1);
766
+ const keep = new Set([1, pageCount, page - 1, page, page + 1].filter((value) => value >= 1 && value <= pageCount));
767
+ const sorted = [...keep].sort((a, b) => a - b);
768
+ const result = [];
769
+ for (let index = 0; index < sorted.length; index += 1) {
770
+ const value = sorted[index];
771
+ const previous = sorted[index - 1];
772
+ if (index > 0 && value - previous > 1) result.push("ellipsis-" + previous);
773
+ result.push(value);
774
+ }
775
+ return result;
776
+ }
777
+
778
+ function Pagination({ page, pageCount, pageSize, setPage, setPageSize, disabled, t, showPageSize = true }) {
779
+ const [sizeOpen, setSizeOpen] = react.useState(false);
780
+ const sizeItems = [20, 50, 100].map((size) => ({ id: String(size), label: String(size) }));
781
+ const sizePicker = showPageSize ? h(Menu, {
782
+ open: sizeOpen,
783
+ compact: true,
784
+ align: "end",
785
+ portal: true,
786
+ selectedId: String(pageSize),
787
+ items: sizeItems,
788
+ onClose: () => setSizeOpen(false),
789
+ onSelect: (id) => { setSizeOpen(false); setPageSize(Number(id)); },
790
+ anchor: hs("button", {
791
+ id: "pm-page-size",
792
+ type: "button",
793
+ className: "pm-page-size-control",
794
+ onClick: () => setSizeOpen((value) => !value),
795
+ title: `${t("browse.pageSize")}: ${pageSize}`,
796
+ "aria-label": `${t("browse.pageSize")}: ${pageSize}`,
797
+ "aria-haspopup": "menu",
798
+ "aria-expanded": sizeOpen,
799
+ children: [
800
+ h(IconFlatListOutlineRegular, { size: 14 }),
801
+ h("span", { children: String(pageSize) }),
802
+ h(IconChevronDownOutlineRegular, { size: 12 }),
803
+ ],
804
+ }),
805
+ }) : null;
806
+ return hs("div", { className: "pm-pagination", children: [
807
+ hs("div", { className: "pm-page-controls", children: [
808
+ h("button", { type: "button", className: "pm-page-button", disabled: page <= 1, onClick: () => setPage(Math.max(1, page - 1)), title: t("browse.prev"), "aria-label": t("browse.prev"), children: h(IconChevronLeftOutlineRegular, { size: 14 }) }),
809
+ ...pageTokens(page, pageCount).map((token) => typeof token === "number"
810
+ ? h("button", { type: "button", className: "pm-page-button", "data-current": token === page, onClick: () => setPage(token), children: String(token) }, token)
811
+ : h("span", { className: "pm-page-ellipsis", children: "…" }, token)),
812
+ h("button", { type: "button", className: "pm-page-button", disabled: page >= pageCount, onClick: () => setPage(Math.min(pageCount, page + 1)), title: t("browse.next"), "aria-label": t("browse.next"), children: h(IconChevronRightOutlineRegular, { size: 14 }) }),
813
+ ] }),
814
+ sizePicker,
815
+ ] });
816
+ }
817
+
818
+ function PluginCard({ plugin, t }) {
819
+ const [copyState, setCopyState] = react.useState("");
820
+ const [installState, setInstallState] = react.useState("idle");
821
+ const [installError, setInstallError] = react.useState("");
822
+ const [installProgress, setInstallProgress] = react.useState({ phase: "idle", activity: 0 });
823
+ const [detailsState, setDetailsState] = react.useState({ status: "idle" });
824
+ const command = installCommand(plugin);
825
+ const spec = plugin.install?.spec || "";
826
+ const declaredCompatibility = plugin.evidence?.dshCompatibility;
827
+ const loadDetails = async () => {
828
+ if (detailsState.status === "loading" || detailsState.status === "ready") return;
829
+ const payload = plugin.identity?.package && plugin.version
830
+ ? { package: plugin.identity.package, version: plugin.version }
831
+ : plugin.identity?.repository
832
+ ? { repository: plugin.identity.repository }
833
+ : null;
834
+ if (!payload) return setDetailsState({ status: "ready", data: {} });
835
+ setDetailsState({ status: "loading" });
836
+ try {
837
+ const data = await rpc("details", payload);
838
+ setDetailsState({ status: "ready", data: data ?? {} });
839
+ } catch (error) {
840
+ setDetailsState({ status: "error", error: String(error?.message ?? error) });
841
+ }
842
+ };
843
+ const resolvedCompatibility = declaredCompatibility ?? detailsState.data?.dshCompatibility;
844
+ const dshPeers = detailsState.data?.dshPeers ?? [];
845
+ const peerTitle = dshPeers.map((peer) => `${peer.dependency} ${peer.range}`).join("\n");
846
+ const compatibilityLabel = resolvedCompatibility ? `DSH ${resolvedCompatibility}`
847
+ : dshPeers.length ? t("compat.apiPeers")
848
+ : detailsState.status === "loading" ? t("compat.loading")
849
+ : detailsState.status === "ready" ? t("compat.none") : t("compat.check");
850
+ const releaseDate = plugin.evidence?.releasedAt;
851
+ const repoDate = plugin.evidence?.repositoryUpdatedAt;
852
+ const freshnessDate = releaseDate ?? repoDate;
853
+ const freshnessLabel = releaseDate ? t("released", { age: relativeAge(releaseDate) })
854
+ : repoDate ? t("repoUpdated", { age: relativeAge(repoDate) }) : "";
855
+
856
+ react.useEffect(() => {
857
+ if (!copyState) return undefined;
858
+ const timer = setTimeout(() => setCopyState(""), 1400);
859
+ return () => clearTimeout(timer);
860
+ }, [copyState]);
861
+
862
+ const copy = async () => {
863
+ try {
864
+ if (!command || !globalThis.navigator?.clipboard?.writeText) throw new Error(t("copy.failed"));
865
+ await globalThis.navigator.clipboard.writeText(command);
866
+ setCopyState("ok");
867
+ } catch { setCopyState("error"); }
868
+ };
869
+
870
+ const install = async () => {
871
+ if (!spec || !remote?.pluginManager || installState === "installing" || installState === "installed" || installState === "invalid") return;
872
+ setInstallState("installing");
873
+ setInstallError("");
874
+ setInstallProgress({ phase: "checking", activity: 0 });
875
+ let disposeState;
876
+ let disposeLog;
877
+ try {
878
+ const inspection = await remote.pluginManager.inspect(spec, { registry: null });
879
+ if (!inspection?.ok) throw new Error(inspection?.error?.message || t("install.failed"));
880
+ if (inspection.value?.status !== "accepted") {
881
+ const problem = inspection.value?.problem;
882
+ const reason = inspection.value?.reason || "Plugin spec was rejected";
883
+ if (problem === "not-a-bundle") {
884
+ setInstallState("invalid");
885
+ setInstallProgress({ phase: "idle", activity: 0 });
886
+ setInstallError(reason);
887
+ return;
888
+ }
889
+ throw new Error(reason);
890
+ }
891
+ const requestId = globalThis.crypto?.randomUUID?.();
892
+ if (!requestId) throw new Error("Browser cannot create an install request id");
893
+ setInstallProgress({ phase: "starting", requestId, activity: 0 });
894
+ if (typeof remote?.$on === "function") {
895
+ disposeState = remote.$on("plugin-manager/install-state", (progress) => {
896
+ if (progress?.requestId !== requestId) return;
897
+ setInstallProgress((current) => ({
898
+ ...current,
899
+ phase: progress.phase,
900
+ attempt: progress.attempt,
901
+ activity: (current.activity ?? 0) + 1,
902
+ }));
903
+ });
904
+ disposeLog = remote.$on("plugin-manager/install-log", (chunk) => {
905
+ if (chunk?.requestId !== requestId) return;
906
+ setInstallProgress((current) => ({
907
+ ...current,
908
+ phase: current.phase === "checking" || current.phase === "starting" ? "installing" : current.phase,
909
+ activity: (current.activity ?? 0) + 1,
910
+ }));
911
+ });
912
+ }
913
+ const options = {
914
+ enabled: true,
915
+ registry: inspection.value.registry,
916
+ requestId,
917
+ };
918
+ const result = await remote.pluginManager.installBundle(spec, options);
919
+ if (!result?.ok) throw new Error(result?.error?.message || t("install.failed"));
920
+ if (result.value?.application === "failed" || result.value?.application === "cancelled") {
921
+ const pending = result.value?.pendingBuilds;
922
+ if (Array.isArray(pending) && pending.length) throw new Error(`${t("install.approvalRequired")}: ${pending.join(", ")}`);
923
+ throw new Error(result.value?.error?.diagnostic || result.value?.packageResult?.output || t("install.failed"));
924
+ }
925
+ setInstallProgress((current) => ({ ...current, phase: "done", requestId }));
926
+ setInstallState("installed");
927
+ } catch (error) {
928
+ setInstallProgress((current) => ({ ...current, phase: "failed" }));
929
+ setInstallState("failed");
930
+ setInstallError(String(error?.message ?? error));
931
+ } finally {
932
+ if (typeof disposeState === "function") disposeState();
933
+ if (typeof disposeLog === "function") disposeLog();
934
+ }
935
+ };
936
+
937
+ const sources = plugin.sources ?? [];
938
+ const primarySource = sources[0];
939
+ const requiresVerification = sources.some((source) => source.type === "npm" || source.type === "github");
940
+ const installability = detailsState.data?.installability ?? plugin.evidence?.installability ?? (requiresVerification ? "unknown" : "bundle");
941
+ const installAllowed = Boolean(spec) && (!requiresVerification || installability === "bundle");
942
+ const bundleLabel = installability === "bundle" ? t("bundle.verified")
943
+ : installability === "not-bundle" ? t("bundle.notBundle") : t("bundle.unknown");
944
+ const progressTitle = installProgress.phase === "checking" ? t("install.checking")
945
+ : installProgress.phase === "starting" ? t("install.starting")
946
+ : installProgress.phase === "installing"
947
+ ? installProgress.attempt
948
+ ? t("install.runningAttempt", { index: installProgress.attempt.index, total: installProgress.attempt.total })
949
+ : t("install.running")
950
+ : installProgress.phase === "applying" ? t("install.applying")
951
+ : t("installing");
952
+ const installTitle = installState === "installing" ? progressTitle
953
+ : installState === "installed" ? t("installed")
954
+ : installState === "invalid" ? t("install.notBundle")
955
+ : installState === "failed" ? t("install.retry") : t("install");
956
+ const installIcon = installState === "installing" ? h(StateDot, { state: "ongoing", size: 16 })
957
+ : installState === "installed" ? h(IconCheckOutlineRegular, { size: 16 })
958
+ : installState === "failed" || installState === "invalid" ? h(IconWarningOutlineRegular, { size: 16 })
959
+ : h(IconDownloadOutlineRegular, { size: 16 });
960
+
961
+ return hs("article", { className: "pm-card", children: [
962
+ hs("div", { className: "pm-card-top", children: [
963
+ hs("div", { className: "pm-card-main", children: [
964
+ hs("div", { className: "pm-card-titleline", children: [
965
+ plugin.identity?.package
966
+ ? h(ExternalLink, { href: npmPackageUrl(plugin.identity.package), className: "pm-plugin-name pm-link", title: plugin.identity.package, children: plugin.name })
967
+ : plugin.identity?.repository
968
+ ? h(ExternalLink, { href: plugin.identity.repository, className: "pm-plugin-name pm-link", title: plugin.identity.repository, children: plugin.name })
969
+ : h("span", { className: "pm-plugin-name", children: plugin.name }),
970
+ plugin.version ? h("span", { className: "pm-version", children: plugin.version }) : null,
971
+ plugin.evidence?.releaseChannel ? h("span", { className: `pm-channel pm-channel-${plugin.evidence.releaseChannel}`, children: releaseChannelLabel(plugin.evidence.releaseChannel) }) : null,
972
+ ] }),
973
+ hs("div", { className: "pm-card-stats", children: [
974
+ hs("span", { className: "pm-signal", title: "GitHub stars", children: [h("span", { className: "pm-star-icon", children: "★" }), h("span", { children: compactNumber(plugin.evidence?.stars ?? 0) })] }),
975
+ hs("span", { className: "pm-signal", title: "npm downloads in the last 30 days", children: [h(IconDownloadOutlineRegular, { size: 13 }), h("span", { children: `${compactNumber(plugin.evidence?.downloads30d ?? 0)} / 30d` })] }),
976
+ freshnessLabel ? hs("span", { className: "pm-signal", title: freshnessDate, children: [h(IconClockOutlineRegular, { size: 13 }), h("span", { children: freshnessLabel })] }) : null,
977
+ plugin.evidence?.rating !== undefined ? hs("span", { className: "pm-signal", title: plugin.evidence.ratingCount !== undefined ? `${plugin.evidence.ratingCount} ratings` : "Source rating", children: [h("span", { className: "pm-star-icon", children: "★" }), h("span", { children: `${plugin.evidence.rating.toFixed(1)}${plugin.evidence.ratingCount !== undefined ? ` (${compactNumber(plugin.evidence.ratingCount)})` : ""}` })] }) : null,
978
+ ] }),
979
+ ] }),
980
+ installAllowed ? h("button", { type: "button", className: "pm-card-install", "data-state": installState, disabled: installState === "installing" || installState === "installed" || installState === "invalid", onClick: install, title: installTitle, "aria-label": installTitle, children: installIcon }) : null,
981
+ ] }),
982
+ plugin.description ? h(ClampedDescription, { text: plugin.description, t, onExpand: loadDetails }) : null,
983
+ hs("div", { className: "pm-card-meta", children: [
984
+ primarySource ? h("span", { className: "pm-badge", children: primarySource.name }, primarySource.id) : null,
985
+ ...(plugin.tags ?? []).map((tag) => h("span", { className: "pm-tag", children: tag }, tag)),
986
+ requiresVerification ? h("span", { className: `pm-bundle-state pm-bundle-state-${installability}`, title: bundleLabel, children: bundleLabel }) : null,
987
+ h("button", { type: "button", className: "pm-compat pm-compat-button", title: resolvedCompatibility ? `DSH ${resolvedCompatibility}` : peerTitle || t("compat.check"), onClick: () => { void loadDetails(); }, children: compatibilityLabel }),
988
+ detailsState.data?.deprecated ? h("span", { className: "pm-compat pm-deprecated", title: detailsState.data.deprecated, children: t("deprecated") }) : null,
989
+ ] }),
990
+ plugin.identity?.repository ? h(ExternalLink, { href: plugin.identity.repository, className: "pm-repo-link pm-link", title: plugin.identity.repository, children: plugin.identity.repository }) : null,
991
+ dshPeers.length && !resolvedCompatibility && detailsState.status === "ready" ? h("div", { className: "pm-source-meta", children: `DSH API: ${dshPeers.map((peer) => `${peer.dependency} ${peer.range}`).join("; ")}` }) : null,
992
+ command && installAllowed ? hs("div", { className: "pm-command-row", children: [
993
+ h("code", { className: "pm-command", children: command }),
994
+ h("button", { type: "button", className: "pm-icon-btn", onClick: copy, title: copyState === "ok" ? t("copied") : t("copy"), "aria-label": copyState === "ok" ? t("copied") : t("copy"), children: h(IconCopyOutlineRegular, { size: 16 }) }),
995
+ ] }) : null,
996
+ installState === "failed" ? h("div", { className: "pm-install-status", title: installError, children: `${t("install.failed")}: ${installError}` }) : null,
997
+ installState === "invalid" ? h("div", { className: "pm-install-status", title: installError, children: `${t("install.notBundle")}: ${installError}` }) : null,
998
+ h("span", { role: "status", "aria-live": "polite", className: "pm-sr-only", children: copyState === "ok" ? t("copied") : copyState === "error" ? t("copy.failed") : installTitle }),
999
+ ] });
1000
+ }
1001
+
1002
+ function CompactFilter({ id, icon, label, value, onChange, items, active = false }) {
1003
+ const [open, setOpen] = react.useState(false);
1004
+ const selectedId = String(value);
1005
+ const selected = items.find((item) => String(item.id) === selectedId) ?? items[0];
1006
+ return h(Menu, {
1007
+ open,
1008
+ compact: true,
1009
+ align: "start",
1010
+ portal: true,
1011
+ selectedId,
1012
+ items,
1013
+ onClose: () => setOpen(false),
1014
+ onSelect: (next) => { setOpen(false); onChange(next); },
1015
+ anchor: hs("button", {
1016
+ id,
1017
+ type: "button",
1018
+ className: "pm-compact-filter",
1019
+ "data-active": active,
1020
+ title: label,
1021
+ "aria-label": label,
1022
+ "aria-haspopup": "menu",
1023
+ "aria-expanded": open,
1024
+ onClick: () => setOpen((current) => !current),
1025
+ children: [
1026
+ h("span", { className: "pm-compact-filter-icon", children: icon }),
1027
+ h("span", { className: "pm-compact-filter-label", children: selected?.label ?? selectedId }),
1028
+ h(IconChevronDownOutlineRegular, { size: 12 }),
1029
+ ],
1030
+ }),
1031
+ });
1032
+ }
1033
+
1034
+ function BrowseView({ t, state, query, setQuery, refresh, sorts, setSorts, stableOnly, setStableOnly, freshnessDays, setFreshnessDays, tag, setTag, dshMetadata, setDshMetadata, page, setPage, pageSize, setPageSize }) {
1035
+ const plugins = state.data?.plugins ?? [];
1036
+ const total = state.data?.total ?? 0;
1037
+ const pageCount = state.data?.pageCount ?? 1;
1038
+ const currentPage = state.data?.page ?? page;
1039
+ const [starEvidence, setStarEvidence] = react.useState({});
1040
+ const starSignature = plugins.map((plugin) => `${repositoryEvidenceKey(plugin.identity?.repository)}:${plugin.evidence?.stars ?? ""}`).join("|");
1041
+ react.useEffect(() => {
1042
+ if (state.loading || !plugins.length || !connection) return undefined;
1043
+ const repositories = [...new Set(plugins
1044
+ .filter((plugin) => plugin.evidence?.stars === undefined)
1045
+ .map((plugin) => plugin.identity?.repository)
1046
+ .filter((repository) => repositoryEvidenceKey(repository).startsWith("https://github.com/"))
1047
+ )].slice(0, 20);
1048
+ if (!repositories.length) return undefined;
1049
+ const controller = new AbortController();
1050
+ rpc("stars", { repositories }, controller.signal).then((rows) => {
1051
+ if (!Array.isArray(rows)) return;
1052
+ setStarEvidence((current) => {
1053
+ const next = { ...current };
1054
+ for (const row of rows) {
1055
+ const key = repositoryEvidenceKey(row?.repository);
1056
+ if (!key || typeof row?.stars !== "number") continue;
1057
+ next[key] = { stars: row.stars, repositoryUpdatedAt: row.repositoryUpdatedAt };
1058
+ }
1059
+ return next;
1060
+ });
1061
+ }, () => {});
1062
+ return () => controller.abort();
1063
+ }, [starSignature]);
1064
+ const pluginWithAsyncStars = (plugin) => {
1065
+ if (plugin.evidence?.stars !== undefined) return plugin;
1066
+ const evidence = starEvidence[repositoryEvidenceKey(plugin.identity?.repository)];
1067
+ return evidence ? { ...plugin, evidence: { ...(plugin.evidence ?? {}), ...evidence } } : plugin;
1068
+ };
1069
+ const sourceRows = Array.isArray(state.data?.sources) ? state.data.sources : [];
1070
+ const failedSources = sourceRows.filter((row) => row?.health?.ok === false);
1071
+ const hasEnabledSources = sourceRows.length > 0;
1072
+ const allSourcesFailed = hasEnabledSources && failedSources.length === sourceRows.length;
1073
+ const changeQuery = (value) => { setPage(1); setQuery(value); };
1074
+ const changeFreshnessDays = (value) => { setPage(1); setFreshnessDays(Number(value)); };
1075
+ const changeTag = (value) => { setPage(1); setTag(value); };
1076
+ const changeDshMetadata = (value) => { setPage(1); setDshMetadata(value); };
1077
+ const changePageSize = (value) => { setPage(1); setPageSize(Number(value)); };
1078
+ const cycleSort = (key) => {
1079
+ setPage(1);
1080
+ setSorts((current) => {
1081
+ const index = current.findIndex((item) => item.key === key);
1082
+ if (index < 0) return [...current, { key, direction: "desc" }];
1083
+ if (current[index].direction === "desc") return current.map((item, at) => at === index ? { ...item, direction: "asc" } : item);
1084
+ return current.filter((_, at) => at !== index);
1085
+ });
1086
+ };
1087
+ const topPager = h(Pagination, { page: currentPage, pageCount, pageSize, setPage, setPageSize: changePageSize, disabled: state.loading, t });
1088
+ const bottomPager = h(Pagination, { page: currentPage, pageCount, pageSize, setPage, setPageSize: changePageSize, disabled: state.loading, t, showPageSize: false });
1089
+ const controls = hs("div", { className: "pm-controls-row", children: [
1090
+ h("button", { type: "button", className: "pm-filter-toggle", "data-active": stableOnly, title: t("browse.stableOnly"), "aria-label": t("browse.stableOnly"), disabled: state.loading, onClick: () => { setPage(1); setStableOnly(!stableOnly); }, children: h(IconShieldOutlineRegular, { size: 14 }) }),
1091
+ h(CompactFilter, { id: "pm-freshness", icon: h(IconClockOutlineRegular, { size: 14 }), label: t("browse.freshness"), value: freshnessDays, active: freshnessDays > 0, onChange: changeFreshnessDays, items: [
1092
+ { id: "0", label: t("browse.freshness.any") },
1093
+ { id: "30", label: "30d" },
1094
+ { id: "90", label: "90d" },
1095
+ { id: "365", label: "1y" },
1096
+ ] }),
1097
+ h(CompactFilter, { id: "pm-category", icon: h(IconChecklistOutlineRegular, { size: 14 }), label: t("browse.category"), value: tag, active: Boolean(tag), onChange: changeTag, items: [
1098
+ { id: "", label: t("browse.category.any") },
1099
+ ...["ui","theme","provider","workflow","integration","tool","automation","schedule","scheduler","skill","bundle","desktop"].map((value) => ({ id: value, label: value })),
1100
+ ] }),
1101
+ h(CompactFilter, { id: "pm-dsh-metadata", icon: h(IconCodeOutlineRegular, { size: 14 }), label: t("browse.dshMetadata"), value: dshMetadata, active: dshMetadata !== "any", onChange: changeDshMetadata, items: [
1102
+ { id: "any", label: "DSH: any" },
1103
+ { id: "declared", label: "DSH: declared" },
1104
+ { id: "unknown", label: "DSH: unknown" },
1105
+ ] }),
1106
+ hs("div", { className: "pm-sort-row", role: "group", "aria-label": t("browse.sort"), children: [
1107
+ h(SortCriterion, { criterion: "stars", icon: "★", label: t("browse.sort.stars"), sorts, onCycle: cycleSort, t }),
1108
+ h(SortCriterion, { criterion: "downloads", icon: h(IconDownloadOutlineRegular, { size: 14 }), label: t("browse.sort.downloads"), sorts, onCycle: cycleSort, t }),
1109
+ h(SortCriterion, { criterion: "freshness", icon: h(IconClockOutlineRegular, { size: 14 }), label: t("browse.sort.freshness"), sorts, onCycle: cycleSort, t }),
1110
+ h(SortCriterion, { criterion: "name", icon: "A", label: t("browse.sort.name"), sorts, onCycle: cycleSort, t }),
1111
+ ] }),
1112
+ ] });
1113
+
1114
+ let emptyHint = t("browse.emptyHint");
1115
+ if (state.data && !hasEnabledSources) emptyHint = t("browse.noSources");
1116
+ else if (allSourcesFailed) emptyHint = t("browse.sourcesUnavailable");
1117
+
1118
+ return hs("section", { className: "pm-browse-view", children: [
1119
+ hs("form", { className: "pm-toolbar pm-browse-toolbar", onSubmit: (event) => { event.preventDefault(); refresh(); }, children: [
1120
+ hs("div", { className: "pm-search", children: [h(SearchIcon, {}), h("input", { id: "pm-search", type: "search", value: query, onChange: (event) => changeQuery(event.target.value), placeholder: t("browse.search"), "aria-label": t("browse.search") })] }),
1121
+ h("button", { type: "submit", className: "pm-refresh-icon pm-browse-refresh", title: t("browse.refresh"), "aria-label": t("browse.refresh"), children: h(RefreshIcon, { className: state.loading ? "pm-spin" : undefined }) }),
1122
+ ] }),
1123
+ state.loading ? hs("div", { className: "pm-loading-strip", role: "status", "aria-live": "polite", children: [
1124
+ h("span", { className: "pm-loading-spinner", children: h(IconRefreshOutlineRegular, { size: 13, className: "pm-spin" }) }),
1125
+ h("span", { children: state.data ? t("browse.updating") : t("browse.loading") }),
1126
+ h("span", { className: "pm-loading-track", "aria-hidden": true, children: h("span", { className: "pm-loading-bar" }) }),
1127
+ ] }) : null,
1128
+ state.error ? h("p", { className: "pm-notice pm-notice-error pm-browse-state", role: "alert", children: `${t("browse.error")}: ${state.error}` }) : null,
1129
+ state.data ? hs("div", { className: "pm-browse-content", children: [
1130
+ hs("div", { className: "pm-browse-topline", children: [
1131
+ h("p", { className: "pm-count", children: t("browse.results", { count: total }) }),
1132
+ topPager,
1133
+ ] }),
1134
+ controls,
1135
+ allSourcesFailed ? failedSources.map((row) => h("p", {
1136
+ className: "pm-notice pm-notice-error pm-browse-state",
1137
+ children: `${row.name ?? row.id ?? row.source?.name ?? row.source?.id ?? "source"}: ${row.health?.error ?? t("source.error")}`,
1138
+ }, row.source?.id ?? row.source?.name)) : null,
1139
+ !state.loading && !state.error && plugins.length === 0 ? h(Empty, { title: t("browse.empty"), hint: emptyHint }) : null,
1140
+ plugins.length ? h("div", { className: "pm-results", "data-updating": state.loading, children: plugins.map((plugin) => h(PluginCard, { plugin: pluginWithAsyncStars(plugin), t }, plugin.identity?.package || plugin.identity?.repository || plugin.identity?.fallback)) }) : null,
1141
+ plugins.length ? h("div", { className: "pm-browse-bottomline", children: bottomPager }) : null,
1142
+ ] }) : null,
1143
+ ] });
1144
+ }
1145
+
1146
+ function PluginSourcesSection({ t }) {
1147
+ const bound = configForm;
1148
+ const [snapshot, setSnapshot] = react.useState(() => bound?.getSnapshot?.());
1149
+ const [tab, setTab] = react.useState("sources");
1150
+ const [query, setQuery] = react.useState("");
1151
+ const [busy, setBusy] = react.useState(false);
1152
+ const [healthState, setHealthState] = react.useState({ loading: false });
1153
+ const [browseState, setBrowseState] = react.useState({ loading: false });
1154
+ const [browseRevision, setBrowseRevision] = react.useState(0);
1155
+ const [sorts, setSorts] = react.useState([]);
1156
+ const [stableOnly, setStableOnly] = react.useState(false);
1157
+ const [freshnessDays, setFreshnessDays] = react.useState(0);
1158
+ const [tag, setTag] = react.useState("");
1159
+ const [dshMetadata, setDshMetadata] = react.useState("any");
1160
+ const [page, setPage] = react.useState(1);
1161
+ const [pageSize, setPageSize] = react.useState(20);
1162
+ const [defaultsMigrating, setDefaultsMigrating] = react.useState(false);
1163
+ const [healthRevision, setHealthRevision] = react.useState(0);
1164
+ const [connectionRevision, setConnectionRevision] = react.useState(0);
1165
+ react.useEffect(() => bound?.subscribe?.(() => setSnapshot(bound.getSnapshot())), [bound]);
1166
+ react.useEffect(() => {
1167
+ const listener = () => setConnectionRevision((value) => value + 1);
1168
+ connectionResetListeners.add(listener);
1169
+ return () => connectionResetListeners.delete(listener);
1170
+ }, []);
1171
+ const sources = Array.isArray(snapshot?.value?.sources) ? snapshot.value.sources : [];
1172
+ const signature = JSON.stringify(sources);
1173
+ const writable = snapshot?.writable === true;
1174
+ const sourceDefaultsVersion = Number(snapshot?.value?.sourceDefaultsVersion ?? 0);
1175
+ const saveSources = async (next) => { if (!bound || !writable) return; setBusy(true); try { await bound.set("sources", next); } finally { setBusy(false); } };
1176
+
1177
+ react.useEffect(() => {
1178
+ if (!bound || !writable || defaultsMigrating || sourceDefaultsVersion >= 2) return undefined;
1179
+ setDefaultsMigrating(true);
1180
+ const next = [...sources];
1181
+ if (!next.some((source) => source.type === "npm")) next.push({ id: "npm", name: "npm", type: "npm", enabled: true });
1182
+ if (!next.some((source) => source.type === "github")) next.push({ id: "github", name: "GitHub", type: "github", enabled: true });
1183
+ Promise.resolve(next.length === sources.length ? undefined : bound.set("sources", next))
1184
+ .then(() => bound.set("sourceDefaultsVersion", 2))
1185
+ .finally(() => setDefaultsMigrating(false));
1186
+ return undefined;
1187
+ }, [bound, writable, defaultsMigrating, sourceDefaultsVersion, signature]);
1188
+
1189
+ react.useEffect(() => {
1190
+ if (tab !== "sources" || !connection) return undefined;
1191
+ const controller = new AbortController();
1192
+ setHealthState({ loading: true });
1193
+ rpc("browse", { healthOnly: true }, controller.signal).then((data) => setHealthState({ loading: false, data }), (error) => { if (error?.name !== "AbortError") setHealthState({ loading: false, error: String(error?.message ?? error) }); });
1194
+ return () => controller.abort();
1195
+ }, [tab, signature, healthRevision, connectionRevision]);
1196
+
1197
+ react.useEffect(() => {
1198
+ if (tab !== "browse" || !connection) return undefined;
1199
+ const controller = new AbortController();
1200
+ const timer = setTimeout(() => {
1201
+ setBrowseState((current) => ({ ...current, loading: true, error: undefined }));
1202
+ rpc("browse", { query, page, pageSize, sorts, stableOnly, freshnessDays, tag, dshMetadata, refreshRevision: browseRevision }, controller.signal).then((data) => {
1203
+ if (data?.page && data.page !== page) setPage(data.page);
1204
+ setBrowseState({ loading: false, data });
1205
+ }, (error) => { if (error?.name !== "AbortError") setBrowseState({ loading: false, error: String(error?.message ?? error) }); });
1206
+ }, 220);
1207
+ return () => { clearTimeout(timer); controller.abort(); };
1208
+ }, [tab, query, page, pageSize, JSON.stringify(sorts), stableOnly, freshnessDays, tag, dshMetadata, signature, browseRevision, connectionRevision]);
1209
+
1210
+ if (!snapshot || snapshot.status === "unavailable") return h("div", { className: "pm-root", children: h("div", { className: "pm-shell", children: h(Empty, { title: t("unavailable"), hint: "registry-aggregator" }) }) });
1211
+ const selectTab = (next) => { setTab(next); queueMicrotask(() => window.document.getElementById?.(`pm-tab-${next}`)?.focus?.()); };
1212
+ const tabKey = (event) => { if (event.key === "ArrowRight" || event.key === "ArrowLeft") { event.preventDefault(); selectTab(tab === "sources" ? "browse" : "sources"); } };
1213
+ return h("div", { className: "pm-root", children: hs("section", { className: "pm-shell", "aria-labelledby": "pm-title", children: [
1214
+ h("p", { className: "pm-kicker", children: t("kicker") }), h("h2", { id: "pm-title", className: "pm-title", children: t("title") }), h("p", { className: "pm-lead", children: t("lead") }),
1215
+ hs("div", { className: "pm-tabs", role: "tablist", "aria-label": t("title"), children: [h("button", { id: "pm-tab-sources", type: "button", className: "pm-tab", role: "tab", tabIndex: tab === "sources" ? 0 : -1, "aria-controls": "pm-panel-sources", "aria-selected": tab === "sources", "data-active": tab === "sources", onKeyDown: tabKey, onClick: () => selectTab("sources"), children: hs("span", { className: "pm-tab-content", children: [h(DatabaseIcon, {}), h("span", { children: t("tab.sources") })] }) }), h("button", { id: "pm-tab-browse", type: "button", className: "pm-tab", role: "tab", tabIndex: tab === "browse" ? 0 : -1, "aria-controls": "pm-panel-browse", "aria-selected": tab === "browse", "data-active": tab === "browse", onKeyDown: tabKey, onClick: () => selectTab("browse"), children: hs("span", { className: "pm-tab-content", children: [h(GridIcon, {}), h("span", { children: t("tab.browse") })] }) })] }),
1216
+ h("div", { id: `pm-panel-${tab}`, role: "tabpanel", "aria-labelledby": `pm-tab-${tab}`, children: tab === "sources" ? h(SourcesView, { t, sources, writable, saveSources, busy, healthState, refreshHealth: () => setHealthRevision((value) => value + 1) }) : h(BrowseView, { t, state: browseState, query, setQuery, refresh: () => setBrowseRevision((value) => value + 1), sorts, setSorts, stableOnly, setStableOnly, freshnessDays, setFreshnessDays, tag, setTag, dshMetadata, setDshMetadata, page, setPage, pageSize, setPageSize }) }),
1217
+ ] }) });
1218
+ }
1219
+
1220
+ function RegistryAggregatorActivation({ onDismiss, onOpenDetails, t }) {
1221
+ return h(Modal, {
1222
+ open: true,
1223
+ title: t("activation.title"),
1224
+ closeLabel: t("activation.later"),
1225
+ onClose: onDismiss,
1226
+ children: hs("div", { style: { display: "grid", gap: 14 }, children: [
1227
+ h("p", { style: { margin: 0, color: "var(--dsw-alias-label-secondary)", lineHeight: 1.55 }, children: t("activation.body") }),
1228
+ hs("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8 }, children: [
1229
+ h(Button, { onClick: onDismiss, children: t("activation.later") }),
1230
+ h(Button, { onClick: onOpenDetails, children: t("activation.open") }),
1231
+ ] }),
1232
+ ] }),
1233
+ });
1234
+ }
1235
+
1236
+ const inject = ["slots", "locale", "configForms", "connection", "remote", "remote.pluginManager"];
1237
+ function apply(ctx) {
1238
+ configForm = ctx.configForms?.get?.(NS);
1239
+ connection = ctx.connection;
1240
+ remote = ctx.remote;
1241
+ ctx.on?.("connection/reset", () => {
1242
+ for (const listener of [...connectionResetListeners]) listener();
1243
+ });
1244
+ ctx.effect(injectStyle, "plugin-sources: styles");
1245
+ ctx.effect(() => ctx.locale.register(NS, { en, zh }), "plugin-sources: locale");
1246
+ ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({ name: "plugins.bundle.config", key: "@stolyarovmn/dsh-ui-registry-aggregator", locale: NS }, PluginSourcesSection));
1247
+ ctx.slots.inject("plugins.bundle.activation", () => ctx.slots.register({ name: "plugins.bundle.activation", key: "@stolyarovmn/dsh-ui-registry-aggregator", locale: NS }, RegistryAggregatorActivation));
1248
+ }
1249
+
1250
+ exports.apply = apply;
1251
+ exports.inject = inject;
1252
+ return module.exports;
1253
+ }
1254
+ });