@linxin666/dsh-remote-web-ui 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +4 -1
  2. package/lib/client.js.map +1 -0
  3. package/lib/mobile.js +15467 -0
  4. package/lib/mobile.js.map +1 -0
  5. package/lib/types/client/PairFailedNotice.js +24 -0
  6. package/lib/types/client/PairFailedNotice.js.map +1 -0
  7. package/lib/types/client/PhoneIcon.js +9 -0
  8. package/lib/types/client/PhoneIcon.js.map +1 -0
  9. package/lib/types/client/PluginSettingsCard.js +39 -0
  10. package/lib/types/client/PluginSettingsCard.js.map +1 -0
  11. package/lib/types/client/RemoteEntry.js +162 -0
  12. package/lib/types/client/RemoteEntry.js.map +1 -0
  13. package/lib/types/client/RemotePanel.js +34 -0
  14. package/lib/types/client/RemotePanel.js.map +1 -0
  15. package/lib/types/client/RemoteSettingsCard.js +59 -0
  16. package/lib/types/client/RemoteSettingsCard.js.map +1 -0
  17. package/lib/types/client/deep-link.js +118 -0
  18. package/lib/types/client/deep-link.js.map +1 -0
  19. package/lib/types/client/index.js +111 -0
  20. package/lib/types/client/index.js.map +1 -0
  21. package/lib/types/client/locales.js +141 -0
  22. package/lib/types/client/locales.js.map +1 -0
  23. package/lib/types/client/pair-api.js +123 -0
  24. package/lib/types/client/pair-api.js.map +1 -0
  25. package/lib/types/client/settings-form.js +211 -0
  26. package/lib/types/client/settings-form.js.map +1 -0
  27. package/lib/types/gate.js +121 -0
  28. package/lib/types/gate.js.map +1 -0
  29. package/lib/types/index.js +227 -0
  30. package/lib/types/index.js.map +1 -0
  31. package/lib/types/invariant.js +23 -0
  32. package/lib/types/invariant.js.map +1 -0
  33. package/lib/types/lan.js +19 -0
  34. package/lib/types/lan.js.map +1 -0
  35. package/lib/types/mobile/api.js +60 -0
  36. package/lib/types/mobile/api.js.map +1 -0
  37. package/lib/types/mobile/index.js +25 -0
  38. package/lib/types/mobile/index.js.map +1 -0
  39. package/lib/types/mobile/messages.js +449 -0
  40. package/lib/types/mobile/messages.js.map +1 -0
  41. package/lib/types/mobile/messages.test.js +189 -0
  42. package/lib/types/mobile/messages.test.js.map +1 -0
  43. package/lib/types/mobile/mobile-styles.js +853 -0
  44. package/lib/types/mobile/mobile-styles.js.map +1 -0
  45. package/lib/types/mobile/mobile-theme.js +70 -0
  46. package/lib/types/mobile/mobile-theme.test.js +71 -0
  47. package/lib/types/mobile/mux.js +110 -0
  48. package/lib/types/mobile/mux.js.map +1 -0
  49. package/lib/types/mobile/rpc.js +83 -0
  50. package/lib/types/mobile/rpc.js.map +1 -0
  51. package/lib/types/mobile/theme-toggle.js +26 -0
  52. package/lib/types/mobile/views/App.js +102 -0
  53. package/lib/types/mobile/views/App.js.map +1 -0
  54. package/lib/types/mobile/views/ChatView.js +348 -0
  55. package/lib/types/mobile/views/ChatView.js.map +1 -0
  56. package/lib/types/mobile/views/ChatView.test.js +215 -0
  57. package/lib/types/mobile/views/SessionListView.js +108 -0
  58. package/lib/types/mobile/views/SessionListView.js.map +1 -0
  59. package/lib/types/mobile/views/SessionListView.test.js +110 -0
  60. package/lib/types/mobile/views/WorkspaceView.js +43 -0
  61. package/lib/types/mobile/views/WorkspaceView.js.map +1 -0
  62. package/lib/types/mobile-api.js +263 -0
  63. package/lib/types/mobile-api.js.map +1 -0
  64. package/lib/types/mobile-routes.js +71 -0
  65. package/lib/types/mobile-routes.js.map +1 -0
  66. package/lib/types/pairing.js +285 -0
  67. package/lib/types/pairing.js.map +1 -0
  68. package/lib/types/routes.js +327 -0
  69. package/lib/types/routes.js.map +1 -0
  70. package/lib/types/tunnel.js +222 -0
  71. package/lib/types/tunnel.js.map +1 -0
  72. package/package.json +9 -6
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Mobile remote control — browser half. Registers the `remote` dictionaries,
3
+ * the sidebar-foot entry (phone trigger + pairing panel) into the
4
+ * ui-sidebar-declared `sidebar.remote` seat, and runs the phone-side boot
5
+ * flow (pair accept + workspace deep-link + presence heartbeats) plus the
6
+ * one-time failed-pair notice. Export discipline: packages/client/AGENTS.md
7
+ * — the /client surface carries only what cordis loading needs plus types.
8
+ */
9
+ import { createElement } from 'react';
10
+ import { createRoot } from 'react-dom/client';
11
+ import { RemoteEntry } from "./RemoteEntry.js";
12
+ import { PairFailedNotice } from "./PairFailedNotice.js";
13
+ import { RemoteSettingsCard, RemoteSettingsCardController } from "./RemoteSettingsCard.js";
14
+ import { en, zh } from "./locales.js";
15
+ import { PAIR_FAILED_MARKER, runPairBootFlow } from "./deep-link.js";
16
+ import { sendHeartbeat } from "./pair-api.js";
17
+ /** Dictionary namespace owned by this plugin. */
18
+ const NS = 'remote';
19
+ /** Settings namespace the remote-control card edits (the Host plugin registers it). */
20
+ const REMOTE_WEB_UI_NS = 'remote-web-ui';
21
+ /** Heartbeat cadence from a paired phone (presence + revocation liveness). */
22
+ const HEARTBEAT_INTERVAL_MS = 10_000;
23
+ /** Services required by this plugin. */
24
+ export const inject = ['slots', 'locale', 'connection', 'settingsScope', 'remote'];
25
+ /**
26
+ * Register the remote-control surface.
27
+ * @param ctx - client root context.
28
+ */
29
+ export function apply(ctx) {
30
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'remote-web-ui: dictionaries');
31
+ const t = ctx.locale.bind(NS);
32
+ const settingsScope = ctx.settingsScope.bind({ namespace: REMOTE_WEB_UI_NS });
33
+ const enabled = () => {
34
+ const snapshot = settingsScope.getSnapshot();
35
+ return snapshot.status === 'ready'
36
+ ? snapshot.value?.enabled ?? true
37
+ : snapshot.status === 'unavailable';
38
+ };
39
+ // Sidebar foot entry: the shell declares 'sidebar.remote' in unconstrained
40
+ // order, so registration is declaration-aware — slots.inject waits on the
41
+ // declaration, removes the contribution when it collapses, and re-runs
42
+ // after a redeclaration. The entry follows the plugin's enabled setting:
43
+ // toggling it off removes the trigger, toggling it back on re-registers it.
44
+ ctx.slots.inject('sidebar.remote', () => {
45
+ let disposeEntry;
46
+ const syncEntry = () => {
47
+ if (enabled() && disposeEntry === undefined) {
48
+ disposeEntry = ctx.slots.register({ name: 'sidebar.remote', locale: NS }, RemoteEntry);
49
+ }
50
+ else if (!enabled() && disposeEntry !== undefined) {
51
+ disposeEntry();
52
+ disposeEntry = undefined;
53
+ }
54
+ };
55
+ const unsubscribe = settingsScope.subscribe(syncEntry);
56
+ syncEntry();
57
+ return () => {
58
+ unsubscribe();
59
+ disposeEntry?.();
60
+ };
61
+ });
62
+ // Plugin configuration card: one staged form over the `remote-web-ui`
63
+ // settings namespace, contributed to the Web UI plugin group.
64
+ const remoteSettings = new RemoteSettingsCardController(settingsScope);
65
+ ctx.slots.inject('web-ui.plugin.item', () => ctx.slots.register({
66
+ name: 'web-ui.plugin.item',
67
+ id: 'remote-web-ui',
68
+ order: 90,
69
+ locale: NS,
70
+ inject: () => remoteSettings.inject(),
71
+ }, RemoteSettingsCard));
72
+ // Phone-side boot flow + heartbeats. Loopback pages (the desktop) never
73
+ // heartbeat; the server ignores unpaired heartbeats anyway. Both run only
74
+ // while the plugin is enabled.
75
+ let disposeRuntime;
76
+ const syncRuntime = () => {
77
+ if (enabled() && disposeRuntime === undefined) {
78
+ disposeRuntime = ctx.effect(() => {
79
+ const connection = ctx.get('connection');
80
+ const loopback = connection?.isLoopback ?? true;
81
+ runPairBootFlow(ctx, window.location.search);
82
+ if (loopback)
83
+ return () => { };
84
+ const timer = window.setInterval(() => { void sendHeartbeat().catch(() => { }); }, HEARTBEAT_INTERVAL_MS);
85
+ return () => { window.clearInterval(timer); };
86
+ }, 'remote-web-ui: pair flow + heartbeats');
87
+ }
88
+ else if (!enabled() && disposeRuntime !== undefined) {
89
+ disposeRuntime();
90
+ disposeRuntime = undefined;
91
+ }
92
+ };
93
+ settingsScope.subscribe(syncRuntime);
94
+ syncRuntime();
95
+ // One-time failed-pair toast. The accept result lands asynchronously, so
96
+ // the marker check is deferred past the accept round trip.
97
+ ctx.effect(() => {
98
+ const timer = window.setTimeout(() => {
99
+ if (sessionStorage.getItem(PAIR_FAILED_MARKER) === null)
100
+ return;
101
+ sessionStorage.removeItem(PAIR_FAILED_MARKER);
102
+ const mount = document.createElement('div');
103
+ document.body.appendChild(mount);
104
+ const root = createRoot(mount);
105
+ root.render(createElement(PairFailedNotice, { t }));
106
+ // The toast owns its dismissal; the root lives for the page lifetime.
107
+ void root;
108
+ }, 1500);
109
+ return () => { window.clearTimeout(timer); };
110
+ }, 'remote-web-ui: failed-pair notice');
111
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,141 @@
1
+ /** `remote` namespace dictionaries: the mobile remote-control surface copy. */
2
+ /** Simplified Chinese dictionary (the key-set source of truth). */
3
+ export const zh = {
4
+ 'entry.label': '移动端远程控制',
5
+ 'title': '移动端远程控制',
6
+ 'subtitle': '扫码或在手机上打开链接,即可远程控制当前工作区',
7
+ 'card.title': '手机扫码连接',
8
+ 'public.badge': '公网',
9
+ 'status.waiting': '等待手机连接',
10
+ 'status.connected': '已连接 {n} 台设备',
11
+ 'status.disconnected': '已配对设备离线',
12
+ 'status.stopped': '已停止移动端访问',
13
+ 'status.lanRequired': '此功能需要以 --host 0.0.0.0 启动 dsh web,或配置公网地址才能使用',
14
+ 'status.lanRequiredHint': '当前服务仅绑定在 127.0.0.1 且未配置公网地址,手机无法访问。请用 dsh web --host 0.0.0.0 重新启动,或在设置中填写内网穿透的公网地址。',
15
+ 'status.loopbackRequired': '配对面板仅限本机使用',
16
+ 'status.loopbackRequiredHint': '请通过 http://127.0.0.1 打开此页面后重试;手机请使用配对链接访问。',
17
+ 'status.unreachable': '无法连接配对服务',
18
+ 'status.unreachableHint': '请刷新页面后重试。',
19
+ 'pair.hint': '无法扫码?可以在手机上打开链接',
20
+ 'pair.publicHint': '公网链接:手机无需与电脑在同一网络',
21
+ 'address.label': '选择二维码指向的网络',
22
+ 'address.public': '公网地址',
23
+ 'address.lan': '局域网',
24
+ 'address.hint': '公网地址适合手机不在同一网络时使用;局域网地址仅在手机与电脑同一网络时可用。',
25
+ 'pair.expires': '二维码有效至 {time}',
26
+ 'pair.expired': '二维码已过期,请刷新',
27
+ 'pair.failed.title': '配对失败',
28
+ 'pair.failed.detail': '链接无效或已使用,请回到电脑端刷新二维码后重新扫码。',
29
+ 'action.stop': '停止',
30
+ 'action.refresh': '刷新二维码',
31
+ 'action.copy': '复制链接',
32
+ 'action.copied': '已复制',
33
+ 'stopped.hint': '已停止移动端访问。点击"刷新二维码"重新开启。',
34
+ 'tunnel.starting': '公网隧道启动中,二维码将自动变为公网链接…',
35
+ 'tunnel.failed': '公网隧道启动失败:{error}',
36
+ 'tunnel.unknownError': '未知错误',
37
+ 'close.label': '关闭移动端远程控制面板',
38
+ // 插件设置卡片(settings.plugin.item 席位)。
39
+ 'settings.title': '移动端远程控制',
40
+ 'settings.description': '配对安全与设备限额。',
41
+ 'settings.enabled': '启用移动端远程控制',
42
+ 'settings.enabledHint': '关闭后移除侧边栏入口并停用配对路由与局域网栅栏。',
43
+ 'settings.tokenTtlMs': '配对令牌有效期(毫秒)',
44
+ 'settings.tokenTtlMsHint': '新生成的二维码链接在此时间后失效。',
45
+ 'settings.offlineAfterMs': '设备离线判定(毫秒)',
46
+ 'settings.offlineAfterMsHint': '配对设备超过该时长未上报心跳即视为离线。',
47
+ 'settings.maxDevices': '已配对设备上限',
48
+ 'settings.maxDevicesHint': '超过上限时淘汰最旧的设备会话。',
49
+ 'settings.cookieName': '设备 Cookie 名',
50
+ 'settings.cookieNameHint': '携带已配对设备标识的 Cookie 名称。',
51
+ 'settings.requirePairingForLan': '局域网访问要求配对',
52
+ 'settings.requirePairingForLanHint': '开启:所有非本机回环的 /api 请求必须携带有效配对 Cookie;关闭:局域网栅栏放开,配对仅用于令牌与状态管理。',
53
+ 'settings.publicBaseUrl': '公网地址(可选)',
54
+ 'settings.publicBaseUrlHint': '内网穿透的公网地址,如 Cloudflare Tunnel:https://xxx.trycloudflare.com。填写后二维码将生成公网链接,手机不在同一网络也能配对;留空则仅局域网可用。',
55
+ 'settings.autoTunnel': '自动公网隧道',
56
+ 'settings.autoTunnelHint': '开启后插件自动启动 Cloudflare quick tunnel(无需安装任何工具),并自动更新公网地址与信任配置,手机随时可用公网配对;关闭时使用上方手动填写的公网地址。',
57
+ 'settings.inherit': '继承',
58
+ 'settings.on': '开',
59
+ 'settings.off': '关',
60
+ 'settings.overridden': '已覆盖',
61
+ 'settings.reset': '恢复默认',
62
+ 'settings.readOnly': '当前部署的设置只读。',
63
+ 'settings.expand': '展开设置',
64
+ 'settings.collapse': '收起设置',
65
+ 'settings.save': '保存',
66
+ 'settings.saving': '保存中…',
67
+ 'settings.discard': '放弃',
68
+ 'settings.unsaved': '未保存',
69
+ 'settings.saveFailed': '部署未接受这些值,已保留供你修改。',
70
+ 'settings.invalidNumber': '请输入数字,留空则使用默认值。',
71
+ };
72
+ /** English dictionary, checked complete against the zh key set. */
73
+ export const en = {
74
+ 'entry.label': 'Mobile remote control',
75
+ 'title': 'Mobile remote control',
76
+ 'subtitle': 'Scan the QR code or open the link on your phone to control this workspace remotely',
77
+ 'card.title': 'Scan to connect',
78
+ 'public.badge': 'Public',
79
+ 'status.waiting': 'Waiting for a phone',
80
+ 'status.connected': '{n} device(s) connected',
81
+ 'status.disconnected': 'Paired devices offline',
82
+ 'status.stopped': 'Mobile access stopped',
83
+ 'status.lanRequired': 'This feature needs dsh web started with --host 0.0.0.0, or a configured public address',
84
+ 'status.lanRequiredHint': 'The server is bound to 127.0.0.1 and no public address is configured, so a phone cannot reach it. Restart with dsh web --host 0.0.0.0, or set the tunneled public address in settings.',
85
+ 'status.loopbackRequired': 'The pairing panel works on this machine only',
86
+ 'status.loopbackRequiredHint': 'Open this page at http://127.0.0.1 to mint a QR code; phones use the paired link.',
87
+ 'status.unreachable': 'Cannot reach the pairing service',
88
+ 'status.unreachableHint': 'Refresh the page and try again.',
89
+ 'pair.hint': 'Cannot scan? Open the link on your phone',
90
+ 'pair.publicHint': 'Public link: the phone does not need to be on the same network',
91
+ 'address.label': 'Network the QR code points to',
92
+ 'address.public': 'Public',
93
+ 'address.lan': 'LAN',
94
+ 'address.hint': 'Use the public address when the phone is not on the same network; LAN addresses only work on the same network.',
95
+ 'pair.expires': 'QR code valid until {time}',
96
+ 'pair.expired': 'QR code expired — refresh it',
97
+ 'pair.failed.title': 'Pairing failed',
98
+ 'pair.failed.detail': 'The link is invalid or was already used. Refresh the QR code on your computer and scan again.',
99
+ 'action.stop': 'Stop',
100
+ 'action.refresh': 'Refresh QR',
101
+ 'action.copy': 'Copy link',
102
+ 'action.copied': 'Copied',
103
+ 'stopped.hint': 'Mobile access is stopped. Click "Refresh QR" to re-enable it.',
104
+ 'tunnel.starting': 'The public tunnel is starting; the QR code will switch to a public link shortly…',
105
+ 'tunnel.failed': 'The public tunnel failed to start: {error}',
106
+ 'tunnel.unknownError': 'unknown error',
107
+ 'close.label': 'Close mobile remote control panel',
108
+ // Plugin settings card (the `settings.plugin.item` seat).
109
+ 'settings.title': 'Mobile remote control',
110
+ 'settings.description': 'Pairing security and device limits.',
111
+ 'settings.enabled': 'Enable mobile remote control',
112
+ 'settings.enabledHint': 'When off, the sidebar entry is removed and pairing routes plus the LAN fence stop.',
113
+ 'settings.tokenTtlMs': 'Pairing token lifetime (ms)',
114
+ 'settings.tokenTtlMsHint': 'How long a minted QR link stays valid before it dies.',
115
+ 'settings.offlineAfterMs': 'Device offline threshold (ms)',
116
+ 'settings.offlineAfterMsHint': 'A paired device flips to offline when it has not been seen for this long.',
117
+ 'settings.maxDevices': 'Paired device cap',
118
+ 'settings.maxDevicesHint': 'Hard cap on paired device sessions; the oldest is evicted when full.',
119
+ 'settings.cookieName': 'Device cookie name',
120
+ 'settings.cookieNameHint': 'Cookie that carries the paired device id.',
121
+ 'settings.requirePairingForLan': 'Require pairing for LAN access',
122
+ 'settings.requirePairingForLanHint': 'On: every non-loopback /api request must carry a live paired-device cookie. Off: the LAN fence is open and pairing only manages tokens/status.',
123
+ 'settings.publicBaseUrl': 'Public address (optional)',
124
+ 'settings.publicBaseUrlHint': 'The public URL of a tunnel in front of this server, e.g. a Cloudflare Tunnel: https://xxx.trycloudflare.com. When set, the QR link is built from it so a phone anywhere can pair; leave blank for LAN-only usage.',
125
+ 'settings.autoTunnel': 'Auto public tunnel',
126
+ 'settings.autoTunnelHint': 'When on, the plugin runs its own Cloudflare quick tunnel (no tool installation needed) and keeps the public address and trust config in sync automatically, so a phone anywhere can pair at any time; when off, the manually entered public address above applies.',
127
+ 'settings.inherit': 'Inherit',
128
+ 'settings.on': 'On',
129
+ 'settings.off': 'Off',
130
+ 'settings.overridden': 'Overridden',
131
+ 'settings.reset': 'Reset to default',
132
+ 'settings.readOnly': 'This deployment stores settings read-only.',
133
+ 'settings.expand': 'Show settings',
134
+ 'settings.collapse': 'Hide settings',
135
+ 'settings.save': 'Save',
136
+ 'settings.saving': 'Saving…',
137
+ 'settings.discard': 'Discard',
138
+ 'settings.unsaved': 'Unsaved',
139
+ 'settings.saveFailed': 'The deployment did not accept these values; they were left for you to correct.',
140
+ 'settings.invalidNumber': 'Enter a number, or leave blank to use the default.',
141
+ };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locales.js","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Browser-side wire helpers for the /api/pair surface. Plain fetch over
3
+ * same-origin /api (like the connection client); JSON bodies, string
4
+ * responses for the error codes.
5
+ */
6
+ /**
7
+ * Mint a fresh pairing token (one active token at a time — this invalidates
8
+ * any previous link).
9
+ * @param workspaceId - optional current workspace to deep-link the phone into.
10
+ * @param address - optional LAN IP literal the QR must be built from (the
11
+ * default is the first interface); unknown literals refuse with
12
+ * 'unknown-address'.
13
+ * @returns the issued link, the lan-required refusal (server never bound
14
+ * 0.0.0.0), or the forbidden refusal (the loopback-only fence rejected this
15
+ * origin — the panel is a desktop control endpoint).
16
+ */
17
+ export async function issuePair(workspaceId, address) {
18
+ const response = await fetch('/api/pair/issue', {
19
+ method: 'POST',
20
+ headers: { 'content-type': 'application/json' },
21
+ body: JSON.stringify({
22
+ ...(workspaceId !== undefined ? { workspaceId } : {}),
23
+ ...(address !== undefined ? { address } : {}),
24
+ }),
25
+ });
26
+ if (!response.ok) {
27
+ if (response.status === 409)
28
+ return { ok: false, code: 'lan-required' };
29
+ if (response.status === 403)
30
+ return { ok: false, code: 'forbidden' };
31
+ if (response.status === 400)
32
+ return { ok: false, code: 'unknown-address' };
33
+ throw new Error(`remote-web-ui: issue failed with ${String(response.status)}`);
34
+ }
35
+ return await response.json();
36
+ }
37
+ /**
38
+ * Accept a pairing token (the phone's first open of the QR link). Success
39
+ * sets the device cookie; the page then reloads to boot with it.
40
+ * @param token - the token from the URL.
41
+ * @returns the wire result.
42
+ */
43
+ export async function acceptPair(token) {
44
+ const response = await fetch('/api/pair/accept', {
45
+ method: 'POST',
46
+ headers: { 'content-type': 'application/json' },
47
+ body: JSON.stringify({ token }),
48
+ });
49
+ if (response.ok)
50
+ return { ok: true };
51
+ if (response.status === 404)
52
+ return { ok: false, code: 'invalid' };
53
+ if (response.status === 409)
54
+ return { ok: false, code: 'used' };
55
+ return { ok: false, code: 'forbidden' };
56
+ }
57
+ /** Revoke mobile access (paired devices + the current token). */
58
+ export async function stopPair() {
59
+ const response = await fetch('/api/pair/stop', { method: 'POST' });
60
+ if (!response.ok)
61
+ throw new Error(`remote-web-ui: stop failed with ${String(response.status)}`);
62
+ }
63
+ /** Presence heartbeat from a paired phone (unpaired heartbeats 401 harmlessly). */
64
+ export async function sendHeartbeat() {
65
+ await fetch('/api/pair/heartbeat', { method: 'POST' });
66
+ }
67
+ /** Whether the current page URL carries a pairing token / workspace target. */
68
+ export function readPairParams(search) {
69
+ const params = new URLSearchParams(search);
70
+ const pair = params.get('pair');
71
+ const workspace = params.get('workspace');
72
+ return {
73
+ ...(pair !== null && pair !== '' ? { pair } : {}),
74
+ ...(workspace !== null && workspace !== '' ? { workspace } : {}),
75
+ };
76
+ }
77
+ /**
78
+ * Strip one query parameter from the current URL without reloading.
79
+ * @param name - the parameter to remove.
80
+ * @returns the new search string ('' when empty).
81
+ */
82
+ export function stripParam(name) {
83
+ const url = new URL(window.location.href);
84
+ url.searchParams.delete(name);
85
+ return url.search;
86
+ } /** Human-readable expiry clock, e.g. "10:35". */
87
+ export function formatClock(epochMs) {
88
+ const date = new Date(epochMs);
89
+ const hours = String(date.getHours()).padStart(2, '0');
90
+ const minutes = String(date.getMinutes()).padStart(2, '0');
91
+ return `${hours}:${minutes}`;
92
+ }
93
+ /**
94
+ * Copy text to the clipboard with a fallback for insecure contexts
95
+ * (plain-HTTP LAN origins lack navigator.clipboard).
96
+ * @param text - the text to copy.
97
+ * @returns whether the copy succeeded.
98
+ */
99
+ export async function copyText(text) {
100
+ if (typeof navigator !== 'undefined' && navigator.clipboard !== undefined) {
101
+ try {
102
+ await navigator.clipboard.writeText(text);
103
+ return true;
104
+ }
105
+ catch {
106
+ // fall through to the execCommand path
107
+ }
108
+ }
109
+ try {
110
+ const area = document.createElement('textarea');
111
+ area.value = text;
112
+ area.style.position = 'fixed';
113
+ area.style.opacity = '0';
114
+ document.body.appendChild(area);
115
+ area.select();
116
+ const ok = document.execCommand('copy');
117
+ area.remove();
118
+ return ok;
119
+ }
120
+ catch {
121
+ return false;
122
+ }
123
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pair-api.js","sourceRoot":"","sources":["../../../src/client/pair-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Staged form model behind the plugin settings card. A card stages what the
3
+ * user types and writes it only when they save — the settings write is a
4
+ * durable, revision-fenced document mutation, so staging keeps what is on
5
+ * screen exactly what a save would store. Mirrors the official
6
+ * ui-plugin-config card-store pattern in a self-contained slice: this
7
+ * package must not depend on a sibling UI package.
8
+ */
9
+ import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
10
+ /** A whole-number field. An empty draft clears the field; any other draft that is not a finite number blocks the save. */
11
+ export function numberField(field) {
12
+ return {
13
+ field,
14
+ format: value => typeof value === 'number' ? String(value) : '',
15
+ parse: (text) => {
16
+ const trimmed = text.trim();
17
+ if (trimmed === '')
18
+ return { kind: 'clear' };
19
+ const parsed = Number(trimmed);
20
+ return Number.isFinite(parsed) ? { kind: 'set', value: parsed } : undefined;
21
+ },
22
+ };
23
+ }
24
+ /** A free-text field. An empty draft clears the field. */
25
+ export function textField(field) {
26
+ return {
27
+ field,
28
+ format: value => typeof value === 'string' ? value : '',
29
+ parse: (text) => {
30
+ const trimmed = text.trim();
31
+ return trimmed === '' ? { kind: 'clear' } : { kind: 'set', value: trimmed };
32
+ },
33
+ };
34
+ }
35
+ /** A boolean field, edited through true/false draft text. */
36
+ export function booleanField(field) {
37
+ return {
38
+ field,
39
+ format: value => typeof value === 'boolean' ? String(value) : '',
40
+ parse: (text) => {
41
+ if (text === 'true')
42
+ return { kind: 'set', value: true };
43
+ if (text === 'false')
44
+ return { kind: 'set', value: false };
45
+ return undefined;
46
+ },
47
+ };
48
+ }
49
+ /**
50
+ * Stages one card's edits over one settings namespace and writes them on save.
51
+ *
52
+ * The Host is the only authority on whether a value was accepted — its
53
+ * validators own the constraints no schema can express — so the outcome is
54
+ * read back from the section rather than predicted here. A save that did not
55
+ * land keeps its drafts, so the user can correct them instead of retyping.
56
+ */
57
+ export class CardForm {
58
+ scope;
59
+ specs;
60
+ staged = new Map();
61
+ listeners = new Set();
62
+ saving = false;
63
+ failed = false;
64
+ /** @param scope - the bound settings scope for this card's namespace. */
65
+ constructor(scope, specs) {
66
+ this.scope = scope;
67
+ this.specs = new Map(specs.map(spec => [spec.field, spec]));
68
+ scope.subscribe(() => { this.publish(); });
69
+ }
70
+ /** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
71
+ bind(project) {
72
+ const store = createSnapshotStore(project());
73
+ this.listeners.add(() => { store.set(project()); });
74
+ return store;
75
+ }
76
+ /** Read the card-level state: what the Host serves, and what a save would do. */
77
+ shell() {
78
+ const snapshot = this.scope.getSnapshot();
79
+ const plan = this.plan();
80
+ return {
81
+ available: snapshot.status === 'ready',
82
+ writable: snapshot.writable,
83
+ dirty: plan.length > 0,
84
+ invalid: plan.some(item => item.run === undefined),
85
+ saving: this.saving,
86
+ failed: this.failed,
87
+ };
88
+ }
89
+ /** Read one field's state from the effective section and its staged draft. */
90
+ field(field) {
91
+ const spec = this.specOf(field);
92
+ const staged = this.staged.get(field);
93
+ if (staged === undefined) {
94
+ return { text: spec.format(this.sectionValue(field)), overridden: this.stored(field), invalid: false };
95
+ }
96
+ const write = staged.clear ? { kind: 'clear' } : spec.parse(staged.text);
97
+ return {
98
+ text: staged.text,
99
+ overridden: write?.kind === 'set',
100
+ invalid: write === undefined,
101
+ };
102
+ }
103
+ /** The actions the card's slot registration injects. */
104
+ actions() {
105
+ return {
106
+ edit: (field, text) => { this.stage(field, { text, clear: false }); },
107
+ resetField: (field) => {
108
+ this.stage(field, { text: this.specOf(field).format(this.baseValue(field)), clear: true });
109
+ },
110
+ save: () => { void this.save(); },
111
+ discard: () => {
112
+ if (this.staged.size === 0 && !this.failed)
113
+ return;
114
+ this.staged.clear();
115
+ this.failed = false;
116
+ this.publish();
117
+ },
118
+ };
119
+ }
120
+ /**
121
+ * Write every staged edit, then re-seed from what the Host accepted.
122
+ * @returns settlement after every write and the read-back.
123
+ */
124
+ async save() {
125
+ const plan = this.plan();
126
+ const writes = plan.flatMap(item => item.run === undefined ? [] : [item.run]);
127
+ if (plan.length === 0 || this.saving || writes.length !== plan.length)
128
+ return;
129
+ this.saving = true;
130
+ this.failed = false;
131
+ this.publish();
132
+ let landed = true;
133
+ for (const write of writes) {
134
+ landed = await write() && landed;
135
+ }
136
+ if (landed)
137
+ this.staged.clear();
138
+ this.saving = false;
139
+ this.failed = !landed;
140
+ this.publish();
141
+ }
142
+ /**
143
+ * Every staged edit a save would write. An entry whose draft is not a value
144
+ * its field accepts carries no write: the form is still dirty, and the save
145
+ * refuses rather than dropping the edit. A staged edit that matches the
146
+ * effective section is not a write at all.
147
+ * @returns the planned writes, in the order the fields were staged.
148
+ */
149
+ plan() {
150
+ const plan = [];
151
+ for (const [field, staged] of this.staged) {
152
+ const spec = this.specOf(field);
153
+ if (staged.clear) {
154
+ if (this.stored(field))
155
+ plan.push({ field, run: () => this.clear(field) });
156
+ continue;
157
+ }
158
+ if (staged.text === spec.format(this.sectionValue(field)))
159
+ continue;
160
+ const write = spec.parse(staged.text);
161
+ if (write === undefined)
162
+ plan.push({ field, run: undefined });
163
+ else if (write.kind === 'clear')
164
+ plan.push({ field, run: () => this.clear(field) });
165
+ else
166
+ plan.push({ field, run: () => this.store(field, write.value) });
167
+ }
168
+ return plan;
169
+ }
170
+ async clear(field) {
171
+ await this.scope.unset(field);
172
+ return !this.stored(field);
173
+ }
174
+ async store(field, value) {
175
+ await this.scope.set(field, value);
176
+ return this.userLayer()?.[field] === value;
177
+ }
178
+ stage(field, edit) {
179
+ this.staged.set(field, edit);
180
+ this.failed = false;
181
+ this.publish();
182
+ }
183
+ specOf(field) {
184
+ const spec = this.specs.get(field);
185
+ // Every call site names a field this card declared; a missing one is a
186
+ // wiring mistake that must not degrade into a silently inert control.
187
+ if (spec === undefined)
188
+ throw new Error(`settings card has no field ${field}`);
189
+ return spec;
190
+ }
191
+ snapshotOf() {
192
+ return this.scope.getSnapshot();
193
+ }
194
+ sectionValue(field) {
195
+ return this.snapshotOf().value?.[field];
196
+ }
197
+ baseValue(field) {
198
+ return this.snapshotOf().base?.[field];
199
+ }
200
+ userLayer() {
201
+ return this.snapshotOf().user;
202
+ }
203
+ stored(field) {
204
+ const user = this.userLayer();
205
+ return user !== undefined && Object.hasOwn(user, field);
206
+ }
207
+ publish() {
208
+ for (const listener of this.listeners)
209
+ listener();
210
+ }
211
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-form.js","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAA;AA0E5E,0HAA0H;AAC1H,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,OAAO;QACL,KAAK;QACL,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;QAC/D,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE;YACd,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;YAC3B,IAAI,OAAO,KAAK,EAAE;gBAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;YAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAA;YAC9B,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;QAC7E,CAAC;KACF,CAAA;AACH,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO;QACL,KAAK;QACL,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACvD,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE;YACd,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;YAC3B,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;QAC7E,CAAC;KACF,CAAA;AACH,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO;QACL,KAAK;QACL,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;QAChE,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE;YACd,IAAI,IAAI,KAAK,MAAM;gBAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;YACxD,IAAI,IAAI,KAAK,OAAO;gBAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;YAC1D,OAAO,SAAS,CAAA;QAClB,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,QAAQ;IASA;IARF,KAAK,CAAwB;IAC7B,MAAM,GAAG,IAAI,GAAG,EAAsB,CAAA;IACtC,SAAS,GAAG,IAAI,GAAG,EAAc,CAAA;IAC1C,MAAM,GAAG,KAAK,CAAA;IACd,MAAM,GAAG,KAAK,CAAA;IAEtB,yEAAyE;IACzE,YACmB,KAAuB,EACxC,KAAkB;QADD,UAAK,GAAL,KAAK,CAAkB;QAGxC,IAAI,CAAC,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;QAC3D,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA,CAAC,CAAC,CAAC,CAAA;IAC3C,CAAC;IAED,wFAAwF;IACxF,IAAI,CAAI,OAAgB;QACtB,MAAM,KAAK,GAAG,mBAAmB,CAAC,OAAO,EAAE,CAAC,CAAA;QAC5C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;QAClD,OAAO,KAAK,CAAA;IACd,CAAC;IAED,iFAAiF;IACjF,KAAK;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAA;QACzC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;QACxB,OAAO;YACL,SAAS,EAAE,QAAQ,CAAC,MAAM,KAAK,OAAO;YACtC,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,KAAK,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC;YACtB,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC;YAClD,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAA;IACH,CAAC;IAED,8EAA8E;IAC9E,KAAK,CAAC,KAAa;QACjB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QACrC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;QACxG,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAgB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACjF,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,UAAU,EAAE,KAAK,EAAE,IAAI,KAAK,KAAK;YACjC,OAAO,EAAE,KAAK,KAAK,SAAS;SAC7B,CAAA;IACH,CAAC;IAED,wDAAwD;IACxD,OAAO;QACL,OAAO;YACL,IAAI,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAA,CAAC,CAAC;YACpE,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE;gBACpB,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;YAC5F,CAAC;YACD,IAAI,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC,IAAI,EAAE,CAAA,CAAC,CAAC;YAChC,OAAO,EAAE,GAAG,EAAE;gBACZ,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM;oBAAE,OAAM;gBAClD,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAA;gBACnB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;gBACnB,IAAI,CAAC,OAAO,EAAE,CAAA;YAChB,CAAC;SACF,CAAA;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;QACxB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;QAC7E,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;YAAE,OAAM;QAC7E,IAAI,CAAC,MAAM,GAAG,IAAI,CAAA;QAClB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;QACnB,IAAI,CAAC,OAAO,EAAE,CAAA;QACd,IAAI,MAAM,GAAG,IAAI,CAAA;QACjB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,GAAG,MAAM,KAAK,EAAE,IAAI,MAAM,CAAA;QAClC,CAAC;QACD,IAAI,MAAM;YAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAA;QAC/B,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;QACnB,IAAI,CAAC,MAAM,GAAG,CAAC,MAAM,CAAA;QACrB,IAAI,CAAC,OAAO,EAAE,CAAA;IAChB,CAAC;IAED;;;;;;OAMG;IACK,IAAI;QACV,MAAM,IAAI,GAAmB,EAAE,CAAA;QAC/B,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YAC/B,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACjB,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;oBAAE,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;gBAC1E,SAAQ;YACV,CAAC;YACD,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;gBAAE,SAAQ;YACnE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;YACrC,IAAI,KAAK,KAAK,SAAS;gBAAE,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAA;iBACxD,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO;gBAAE,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;;gBAC9E,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QACtE,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAEO,KAAK,CAAC,KAAK,CAAC,KAAa;QAC/B,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QAC7B,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAC5B,CAAC;IAEO,KAAK,CAAC,KAAK,CAAC,KAAa,EAAE,KAAc;QAC/C,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;QAClC,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,CAAA;IAC5C,CAAC;IAEO,KAAK,CAAC,KAAa,EAAE,IAAgB;QAC3C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QAC5B,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;QACnB,IAAI,CAAC,OAAO,EAAE,CAAA;IAChB,CAAC;IAEO,MAAM,CAAC,KAAa;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAClC,uEAAuE;QACvE,sEAAsE;QACtE,IAAI,IAAI,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,KAAK,EAAE,CAAC,CAAA;QAC9E,OAAO,IAAI,CAAA;IACb,CAAC;IAEO,UAAU;QAChB,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAA;IACjC,CAAC;IAEO,YAAY,CAAC,KAAa;QAChC,OAAQ,IAAI,CAAC,UAAU,EAAE,CAAC,KAA6C,EAAE,CAAC,KAAK,CAAC,CAAA;IAClF,CAAC;IAEO,SAAS,CAAC,KAAa;QAC7B,OAAQ,IAAI,CAAC,UAAU,EAAE,CAAC,IAA4C,EAAE,CAAC,KAAK,CAAC,CAAA;IACjF,CAAC;IAEO,SAAS;QACf,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,IAA2C,CAAA;IACtE,CAAC;IAEO,MAAM,CAAC,KAAa;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,CAAA;QAC7B,OAAO,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IACzD,CAAC;IAEO,OAAO;QACb,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS;YAAE,QAAQ,EAAE,CAAA;IACnD,CAAC;CACF"}