@satanwagen/reviewkit 0.1.3 → 0.1.5

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.
@@ -906,7 +906,7 @@ __export(client_exports, {
906
906
  module.exports = __toCommonJS(client_exports);
907
907
 
908
908
  // src/client/ReviewKit.tsx
909
- var import_react21 = require("react");
909
+ var import_react22 = require("react");
910
910
  var import_react_dom = require("react-dom");
911
911
 
912
912
  // src/activate.ts
@@ -1908,6 +1908,8 @@ input, textarea, select {
1908
1908
  .rk-sync-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--rk-fg-faint); }
1909
1909
  .rk-sync-online .rk-sync-dot { background: var(--rk-success); }
1910
1910
  .rk-sync-connecting .rk-sync-dot { background: var(--rk-warn); }
1911
+ .rk-sync-reason-token-short .rk-sync-dot, .rk-sync-reason-bad-token .rk-sync-dot { background: var(--rk-danger); }
1912
+ .rk-sync[title] { cursor: help; }
1911
1913
  /* Automatic Emblema push hint (same quiet language) */
1912
1914
  .rk-epush-ok .rk-sync-dot { background: var(--rk-success); }
1913
1915
  .rk-epush-sending .rk-sync-dot { background: var(--rk-warn); }
@@ -3483,7 +3485,8 @@ function reducer(state, action) {
3483
3485
  panelOpen: false,
3484
3486
  confirm: null,
3485
3487
  cheatsheetOpen: false,
3486
- emblemaConnectOpen: false
3488
+ emblemaConnectOpen: false,
3489
+ terminalConnectOpen: false
3487
3490
  } : { ...state, hidden: false };
3488
3491
  case "SELECT":
3489
3492
  return { ...state, selection: action.selection };
@@ -3593,12 +3596,16 @@ function reducer(state, action) {
3593
3596
  return { ...state, cheatsheetOpen: action.open };
3594
3597
  case "SET_EMBLEMA_CONNECT":
3595
3598
  return { ...state, emblemaConnectOpen: action.open };
3599
+ case "SET_TERMINAL_CONNECT":
3600
+ return { ...state, terminalConnectOpen: action.open };
3596
3601
  case "SET_COACH":
3597
3602
  return { ...state, coachOpen: action.open };
3598
3603
  case "SET_CORNER":
3599
3604
  return { ...state, badgeCorner: action.corner };
3600
- case "SYNC_STATUS":
3601
- return state.syncStatus === action.status ? state : { ...state, syncStatus: action.status };
3605
+ case "SYNC_STATUS": {
3606
+ const reason = action.reason === void 0 ? action.status === "online" ? null : state.syncReason : action.reason;
3607
+ return state.syncStatus === action.status && state.syncReason === reason ? state : { ...state, syncStatus: action.status, syncReason: reason };
3608
+ }
3602
3609
  case "EMBLEMA_PUSH_STATUS":
3603
3610
  return state.emblemaPush === action.status ? state : { ...state, emblemaPush: action.status };
3604
3611
  /*
@@ -3727,9 +3734,11 @@ function initialState() {
3727
3734
  confirm: null,
3728
3735
  cheatsheetOpen: false,
3729
3736
  emblemaConnectOpen: false,
3737
+ terminalConnectOpen: false,
3730
3738
  coachOpen: false,
3731
3739
  undoStack: [],
3732
3740
  syncStatus: "disabled",
3741
+ syncReason: null,
3733
3742
  emblemaPush: "idle",
3734
3743
  peers: [],
3735
3744
  quick: null,
@@ -3744,7 +3753,7 @@ function StoreProvider({ config, children }) {
3744
3753
  const configValue = (0, import_react.useMemo)(
3745
3754
  () => config,
3746
3755
  // eslint-disable-next-line react-hooks/exhaustive-deps -- config object identity is per-render; depend on its fields
3747
- [config.enabled, config.token, config.devAutoOn, config.syncToken, config.syncUrl, config.defaultAuthor, config.fxAssetsUrl, config.position, config.storageKey, config.onSessionChange, config.emblema]
3756
+ [config.enabled, config.token, config.devAutoOn, config.sync, config.syncToken, config.syncUrl, config.defaultAuthor, config.fxAssetsUrl, config.position, config.storageKey, config.onSessionChange, config.emblema]
3748
3757
  );
3749
3758
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ConfigContext.Provider, { value: configValue, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StateContext.Provider, { value: state, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DispatchContext.Provider, { value: dispatch, children }) }) });
3750
3759
  }
@@ -3773,7 +3782,7 @@ function toast(dispatch, message, tone = "info") {
3773
3782
  }
3774
3783
 
3775
3784
  // src/client/App.tsx
3776
- var import_react20 = require("react");
3785
+ var import_react21 = require("react");
3777
3786
 
3778
3787
  // src/client/persist.ts
3779
3788
  var DB_NAME = "review-kit";
@@ -4757,9 +4766,13 @@ function createRkSync(options) {
4757
4766
  pongTimer = null;
4758
4767
  }
4759
4768
  break;
4760
- case "error":
4769
+ case "error": {
4761
4770
  log("server error:", msg.code, msg.message);
4771
+ const code = typeof msg.code === "string" ? msg.code : "error";
4772
+ const message = typeof msg.message === "string" ? msg.message : "";
4773
+ safely(() => handlers.onError?.(code, message));
4762
4774
  break;
4775
+ }
4763
4776
  case "presence:join": {
4764
4777
  const peer = msg.peer;
4765
4778
  if (peer && typeof peer.id === "string") safely(() => handlers.onPeerJoin?.(peer));
@@ -4961,6 +4974,23 @@ function currentAuthor(name) {
4961
4974
  }
4962
4975
 
4963
4976
  // src/client/sync.ts
4977
+ var DEFAULT_SYNC_URL = "wss://2-59-219-26.sslip.io/rk-sync";
4978
+ function normalizeRoute(route) {
4979
+ let r = String(route ?? "/");
4980
+ if (!r.startsWith("/")) r = "/" + r;
4981
+ return r.length > 1 ? r.replace(/\/+$/, "") : r;
4982
+ }
4983
+ function resolveSyncUrl(config) {
4984
+ return typeof config.syncUrl === "string" && config.syncUrl !== "" ? config.syncUrl : DEFAULT_SYNC_URL;
4985
+ }
4986
+ var MIN_SYNC_TOKEN_LENGTH = 8;
4987
+ var warnedShortToken = false;
4988
+ function resolveSyncToken(config) {
4989
+ if (config.sync === false) return null;
4990
+ const explicit = typeof config.syncToken === "string" && config.syncToken !== "" ? config.syncToken : null;
4991
+ const fallback = typeof config.token === "string" && config.token !== "" ? config.token : null;
4992
+ return explicit ?? fallback;
4993
+ }
4964
4994
  function toPeerState(peer) {
4965
4995
  return {
4966
4996
  id: peer.id,
@@ -4988,20 +5018,43 @@ function useRealtimeSync(state, rawDispatch, config, reducer2) {
4988
5018
  const stateRef = (0, import_react4.useRef)(state);
4989
5019
  stateRef.current = state;
4990
5020
  const myIdRef = (0, import_react4.useRef)("");
4991
- const enabled = typeof config.syncToken === "string" && config.syncToken !== "" && typeof config.syncUrl === "string" && config.syncUrl !== "";
5021
+ const syncToken = resolveSyncToken(config);
5022
+ const syncUrl = resolveSyncUrl(config);
5023
+ const enabled = syncToken !== null;
4992
5024
  const author = state.session?.author ?? "";
4993
5025
  const hasSession = state.session !== null;
4994
5026
  (0, import_react4.useEffect)(() => {
4995
5027
  if (!enabled || !hasSession) return;
5028
+ const dispatchGuard = rawDispatch;
5029
+ if (syncToken.length < MIN_SYNC_TOKEN_LENGTH) {
5030
+ dispatchGuard({ type: "SYNC_STATUS", status: "offline", reason: "token-short" });
5031
+ if (!warnedShortToken) {
5032
+ warnedShortToken = true;
5033
+ console.warn(
5034
+ `[review-kit] live sync is off: the token has ${syncToken.length} characters, the sync server needs at least ${MIN_SYNC_TOKEN_LENGTH}. Use a longer \`token\` (or \`syncToken\`) \u2014 it is the secret of your private rooms.`
5035
+ );
5036
+ }
5037
+ return () => dispatchGuard({ type: "SYNC_STATUS", status: "disabled", reason: null });
5038
+ }
4996
5039
  const id = reviewerId();
4997
5040
  myIdRef.current = id;
4998
5041
  const dispatch = rawDispatch;
4999
5042
  const sync = createRkSync({
5000
- token: config.syncToken,
5001
- url: config.syncUrl,
5043
+ token: syncToken,
5044
+ url: syncUrl,
5002
5045
  user: { id, name: author || "Reviewer", color: colorForReviewer(id) },
5003
5046
  handlers: {
5004
- onStatus: (status) => dispatch({ type: "SYNC_STATUS", status }),
5047
+ onStatus: (status) => dispatch({
5048
+ type: "SYNC_STATUS",
5049
+ status,
5050
+ // Offline with no server verdict = the socket never got through
5051
+ // (server down, CSP connect-src, network); a verdict keeps its reason.
5052
+ ...status === "offline" ? { reason: stateRef.current.syncReason ?? "unreachable" } : {}
5053
+ }),
5054
+ onError: (code) => {
5055
+ if (code === "bad-token") dispatch({ type: "SYNC_STATUS", status: "offline", reason: "bad-token" });
5056
+ else if (code === "room-full") dispatch({ type: "SYNC_STATUS", status: "offline", reason: "room-full" });
5057
+ },
5005
5058
  onSnapshot: (items, peers, deletedIds = []) => {
5006
5059
  const safeItems = items.map(sanitizeRemoteItem).filter((item) => item !== null);
5007
5060
  dispatch({ type: "SYNC_SNAPSHOT", items: safeItems });
@@ -5009,8 +5062,11 @@ function useRealtimeSync(state, rawDispatch, config, reducer2) {
5009
5062
  for (const id2 of deletedIds) dispatch({ type: "REMOTE_DELETE", id: id2 });
5010
5063
  dispatch({ type: "PEERS_SET", peers: peers.filter((peer) => peer.id !== id).map(toPeerState) });
5011
5064
  const serverIds = new Set(items.map((item) => item.id));
5065
+ const here = normalizeRoute(window.location.pathname);
5012
5066
  for (const item of stateRef.current.session?.items ?? []) {
5013
- if (!serverIds.has(item.id) && !deleted.has(item.id)) sync.addItem(item);
5067
+ if (serverIds.has(item.id) || deleted.has(item.id)) continue;
5068
+ if (normalizeRoute(item.route) !== here) continue;
5069
+ sync.addItem(item);
5014
5070
  }
5015
5071
  },
5016
5072
  onPeerJoin: (peer) => dispatch({
@@ -5058,10 +5114,10 @@ function useRealtimeSync(state, rawDispatch, config, reducer2) {
5058
5114
  if (activeSyncRef === sync) activeSyncRef = null;
5059
5115
  setEffectBroadcaster2(null);
5060
5116
  sync.stop();
5061
- dispatch({ type: "SYNC_STATUS", status: "disabled" });
5117
+ dispatch({ type: "SYNC_STATUS", status: "disabled", reason: null });
5062
5118
  dispatch({ type: "PEERS_SET", peers: [] });
5063
5119
  };
5064
- }, [enabled, hasSession, config.syncToken, config.syncUrl, author, rawDispatch]);
5120
+ }, [enabled, hasSession, syncToken, syncUrl, author, rawDispatch]);
5065
5121
  const wrapped = (0, import_react4.useMemo)(() => {
5066
5122
  return (action) => {
5067
5123
  const prev = stateRef.current;
@@ -5577,10 +5633,10 @@ function EmblemaConnect() {
5577
5633
  state === null && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "rk-modal-text", children: [
5578
5634
  "The local sync agent isn't running. In the project repo run:",
5579
5635
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("code", { className: "rk-connect-cmd", children: [
5580
- "npx @satanwagen/reviewkit-cli init --origin ",
5636
+ "npx -p @satanwagen/reviewkit-cli reviewkit-emblema init --origin ",
5581
5637
  window.location.origin
5582
5638
  ] }),
5583
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("code", { className: "rk-connect-cmd", children: "npx @satanwagen/reviewkit-cli serve" }),
5639
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("code", { className: "rk-connect-cmd", children: "npx -p @satanwagen/reviewkit-cli reviewkit-emblema serve" }),
5584
5640
  "then reopen this dialog."
5585
5641
  ] }),
5586
5642
  state !== null && state !== "loading" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
@@ -5667,15 +5723,229 @@ function EmblemaConnect() {
5667
5723
  ] });
5668
5724
  }
5669
5725
 
5670
- // src/client/components/CoachMark.tsx
5726
+ // src/client/components/TerminalConnect.tsx
5671
5727
  var import_react7 = require("react");
5728
+
5729
+ // src/integrations/terminal.ts
5730
+ var TERMINAL_PAIR_PORTS = [48780, 48781, 48782, 48783];
5731
+ async function fetchJson2(url, init, timeoutMs) {
5732
+ const abort = new AbortController();
5733
+ const timer = window.setTimeout(() => abort.abort(), timeoutMs);
5734
+ try {
5735
+ return await fetch(url, { ...init, signal: abort.signal });
5736
+ } finally {
5737
+ window.clearTimeout(timer);
5738
+ }
5739
+ }
5740
+ async function probeTerminal() {
5741
+ for (const port of TERMINAL_PAIR_PORTS) {
5742
+ try {
5743
+ const res = await fetchJson2(`http://127.0.0.1:${port}/pair`, { method: "GET" }, 900);
5744
+ if (!res.ok) continue;
5745
+ const body = await res.json();
5746
+ if (typeof body === "object" && body !== null && body.app === "reviewkit-cli") {
5747
+ const b = body;
5748
+ return {
5749
+ port,
5750
+ cwd: typeof b.cwd === "string" ? b.cwd : void 0,
5751
+ projects: Array.isArray(b.projects) ? b.projects.filter((p) => typeof p === "string") : []
5752
+ };
5753
+ }
5754
+ } catch {
5755
+ }
5756
+ }
5757
+ return null;
5758
+ }
5759
+ async function pairTerminal(port, key) {
5760
+ try {
5761
+ const res = await fetchJson2(
5762
+ `http://127.0.0.1:${port}/pair`,
5763
+ { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ key }) },
5764
+ 5e3
5765
+ );
5766
+ const body = await res.json().catch(() => null);
5767
+ if (res.ok && body?.ok) return { ok: true, name: body.name ?? "", repo: body.repo ?? null };
5768
+ return { ok: false, error: body?.error ?? `HTTP ${res.status}` };
5769
+ } catch {
5770
+ return { ok: false, error: "the terminal stopped answering" };
5771
+ }
5772
+ }
5773
+
5774
+ // src/client/terminalKey.ts
5775
+ var TERMINAL_KEY_PREFIX = "rkc_";
5776
+ var TERMINAL_KEY_VERSION = 1;
5777
+ function base64url(text) {
5778
+ const bytes = new TextEncoder().encode(text);
5779
+ let binary = "";
5780
+ for (const byte of bytes) binary += String.fromCharCode(byte);
5781
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
5782
+ }
5783
+ function encodeTerminalKey(fields) {
5784
+ const payload = { v: TERMINAL_KEY_VERSION, ...fields };
5785
+ return TERMINAL_KEY_PREFIX + base64url(JSON.stringify(payload));
5786
+ }
5787
+ function currentTerminalKey(syncUrl, syncToken) {
5788
+ const origin = window.location.origin;
5789
+ let route = "/";
5790
+ try {
5791
+ route = window.location.pathname || "/";
5792
+ } catch {
5793
+ }
5794
+ return encodeTerminalKey({ name: window.location.hostname, origin, syncUrl, syncToken, route });
5795
+ }
5796
+
5797
+ // src/client/components/TerminalConnect.tsx
5672
5798
  var import_jsx_runtime6 = require("react/jsx-runtime");
5799
+ function shortKey2(key) {
5800
+ return `${key.slice(0, 8)}\u2026${key.slice(-6)}`;
5801
+ }
5802
+ function shortPath(path) {
5803
+ if (!path) return "";
5804
+ const parts = path.split("/").filter(Boolean);
5805
+ return parts.length > 2 ? `\u2026/${parts.slice(-2).join("/")}` : path;
5806
+ }
5807
+ function TerminalConnect() {
5808
+ const { terminalConnectOpen } = useStore();
5809
+ const config = useConfig();
5810
+ const dispatch = useDispatch();
5811
+ const [phase, setPhase] = (0, import_react7.useState)("probing");
5812
+ const [probe, setProbe] = (0, import_react7.useState)(null);
5813
+ const [paired, setPaired] = (0, import_react7.useState)(null);
5814
+ const [showManual, setShowManual] = (0, import_react7.useState)(false);
5815
+ const syncToken = resolveSyncToken(config);
5816
+ const key = terminalConnectOpen && syncToken ? currentTerminalKey(resolveSyncUrl(config), syncToken) : null;
5817
+ (0, import_react7.useEffect)(() => {
5818
+ if (!terminalConnectOpen) return;
5819
+ let cancelled = false;
5820
+ setPhase("probing");
5821
+ setProbe(null);
5822
+ setPaired(null);
5823
+ setShowManual(false);
5824
+ const look = () => void probeTerminal().then((found) => {
5825
+ if (cancelled) return;
5826
+ setProbe(found);
5827
+ setPhase((current) => current === "pairing" || current === "paired" ? current : found ? "found" : "missing");
5828
+ });
5829
+ look();
5830
+ const timer = window.setInterval(look, 2e3);
5831
+ return () => {
5832
+ cancelled = true;
5833
+ window.clearInterval(timer);
5834
+ };
5835
+ }, [terminalConnectOpen]);
5836
+ if (!terminalConnectOpen) return null;
5837
+ const close = () => dispatch({ type: "SET_TERMINAL_CONNECT", open: false });
5838
+ const copy = async (value, what) => {
5839
+ try {
5840
+ await navigator.clipboard.writeText(value);
5841
+ toast(dispatch, `${what} copied`, "success");
5842
+ } catch {
5843
+ toast(dispatch, "Copy failed \u2014 select the text manually", "error");
5844
+ }
5845
+ };
5846
+ const connect = () => {
5847
+ if (!probe || !key) return;
5848
+ setPhase("pairing");
5849
+ void pairTerminal(probe.port, key).then((result) => {
5850
+ if (result.ok) {
5851
+ setPaired({ name: result.name, repo: result.repo });
5852
+ setPhase("paired");
5853
+ toast(dispatch, `Terminal paired as \u201C${result.name}\u201D`, "success");
5854
+ } else {
5855
+ setPhase("found");
5856
+ toast(dispatch, `Pairing failed: ${result.error}`, "error");
5857
+ }
5858
+ });
5859
+ };
5860
+ const connectCmd = key ? `npx -y @satanwagen/reviewkit-cli connect ${key}` : "";
5861
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
5862
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "rk-backdrop", onClick: close }),
5863
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "rk-modal", role: "dialog", "aria-modal": "true", "aria-label": "Connect terminal or agent", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "rk-modal-body", children: [
5864
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "rk-modal-title", style: { display: "flex", alignItems: "center", gap: 8 }, children: [
5865
+ "Connect terminal / agent",
5866
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { flex: 1 } }),
5867
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "rk-icon-btn", "aria-label": "Close", onClick: close, autoFocus: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(IconClose, { size: 13 }) })
5868
+ ] }),
5869
+ !key && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "rk-modal-text", children: [
5870
+ "Live sync is off on this page (",
5871
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { children: "sync=false" }),
5872
+ " or no token), so there is no shared room a terminal could read."
5873
+ ] }),
5874
+ key && phase === "paired" && paired && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "rk-modal-text", children: [
5875
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "rk-emblema-dot", "data-state": "live" }),
5876
+ " Paired as ",
5877
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: paired.name }),
5878
+ paired.repo ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
5879
+ " in ",
5880
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { children: shortPath(paired.repo) })
5881
+ ] }) : null,
5882
+ ". The terminal shows this page's comments live, and Claude Code / Cursor can read and resolve them through the ",
5883
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { children: "reviewkit" }),
5884
+ " MCP tools."
5885
+ ] }),
5886
+ key && phase !== "paired" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
5887
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "rk-modal-text", children: [
5888
+ phase === "probing" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
5889
+ "Looking for a ",
5890
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { children: "reviewkit" }),
5891
+ " terminal on this machine\u2026"
5892
+ ] }),
5893
+ phase === "missing" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
5894
+ "No terminal is listening yet. In the site's repo run",
5895
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { className: "rk-connect-cmd", children: "npx -y @satanwagen/reviewkit-cli setup" }),
5896
+ "and this dialog will pick it up by itself."
5897
+ ] }),
5898
+ (phase === "found" || phase === "pairing") && probe && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
5899
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "rk-emblema-dot", "data-state": "live" }),
5900
+ " Terminal found",
5901
+ probe.cwd ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
5902
+ " in ",
5903
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { children: shortPath(probe.cwd) })
5904
+ ] }) : null,
5905
+ ".",
5906
+ probe.projects.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
5907
+ " Already paired: ",
5908
+ probe.projects.join(", "),
5909
+ "."
5910
+ ] }) : null
5911
+ ] })
5912
+ ] }),
5913
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "rk-connect-actions", children: [
5914
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5915
+ "button",
5916
+ {
5917
+ type: "button",
5918
+ className: "rk-btn rk-btn-primary",
5919
+ disabled: phase !== "found",
5920
+ onClick: connect,
5921
+ title: "Send this site's connection to the terminal \u2014 nothing to copy",
5922
+ children: phase === "pairing" ? "Connecting\u2026" : "Connect this terminal"
5923
+ }
5924
+ ),
5925
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => setShowManual((v) => !v), children: showManual ? "Hide manual option" : "Another machine\u2026" })
5926
+ ] }),
5927
+ showManual && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
5928
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "rk-modal-text", style: { marginTop: 10 }, children: "On a machine without a listening terminal, run this once (the key carries the sync server and its token \u2014 treat it like a review link):" }),
5929
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("code", { className: "rk-connect-cmd", title: connectCmd, children: [
5930
+ "npx -y @satanwagen/reviewkit-cli connect ",
5931
+ shortKey2(key)
5932
+ ] }),
5933
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "rk-connect-actions", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => void copy(connectCmd, "Command"), children: "Copy command" }) })
5934
+ ] })
5935
+ ] })
5936
+ ] }) })
5937
+ ] });
5938
+ }
5939
+
5940
+ // src/client/components/CoachMark.tsx
5941
+ var import_react8 = require("react");
5942
+ var import_jsx_runtime7 = require("react/jsx-runtime");
5673
5943
  var COACH_KEY = "review-kit:coach-dismissed";
5674
5944
  function CoachMark() {
5675
5945
  const { coachOpen, session } = useStore();
5676
5946
  const dispatch = useDispatch();
5677
5947
  const config = useConfig();
5678
- const [name, setName] = (0, import_react7.useState)(session?.author ?? config.defaultAuthor ?? "");
5948
+ const [name, setName] = (0, import_react8.useState)(session?.author ?? config.defaultAuthor ?? "");
5679
5949
  if (!coachOpen) return null;
5680
5950
  const done = (startPicking) => {
5681
5951
  if (name.trim() !== "") dispatch({ type: "SET_AUTHOR", author: name.trim() });
@@ -5686,39 +5956,39 @@ function CoachMark() {
5686
5956
  }
5687
5957
  if (startPicking) dispatch({ type: "SET_MODE", mode: "pick" });
5688
5958
  };
5689
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "rk-coach", role: "dialog", "aria-label": "Welcome to review mode", children: [
5690
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "rk-coach-accent" }),
5691
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "rk-coach-body", children: [
5692
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "rk-modal-title", style: { display: "flex", alignItems: "center", gap: 8 }, children: [
5693
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(IconTarget, { size: 16 }),
5959
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rk-coach", role: "dialog", "aria-label": "Welcome to review mode", children: [
5960
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rk-coach-accent" }),
5961
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rk-coach-body", children: [
5962
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rk-modal-title", style: { display: "flex", alignItems: "center", gap: 8 }, children: [
5963
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconTarget, { size: 16 }),
5694
5964
  " Review mode"
5695
5965
  ] }),
5696
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "rk-modal-text", children: "Click anything on the page to suggest a change \u2014 rewrite the copy, swap an image, or leave a note. The live site stays untouched; everything you add is collected for the team." }),
5697
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "rk-coach-keys", "aria-label": "Key shortcuts", children: [
5698
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
5699
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "rk-kbd", children: "T" }),
5966
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rk-modal-text", children: "Click anything on the page to suggest a change \u2014 rewrite the copy, swap an image, or leave a note. The live site stays untouched; everything you add is collected for the team." }),
5967
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rk-coach-keys", "aria-label": "Key shortcuts", children: [
5968
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { children: [
5969
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "rk-kbd", children: "T" }),
5700
5970
  " rewrite"
5701
5971
  ] }),
5702
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
5703
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "rk-kbd", children: "C" }),
5972
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { children: [
5973
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "rk-kbd", children: "C" }),
5704
5974
  " comment"
5705
5975
  ] }),
5706
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
5707
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "rk-kbd", children: "I" }),
5976
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { children: [
5977
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "rk-kbd", children: "I" }),
5708
5978
  " image"
5709
5979
  ] }),
5710
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
5711
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "rk-kbd", children: "V" }),
5980
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { children: [
5981
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "rk-kbd", children: "V" }),
5712
5982
  " select"
5713
5983
  ] }),
5714
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
5715
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "rk-kbd", children: "?" }),
5984
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { children: [
5985
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "rk-kbd", children: "?" }),
5716
5986
  " all shortcuts"
5717
5987
  ] })
5718
5988
  ] }),
5719
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
5720
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("label", { className: "rk-label", htmlFor: "rk-author", children: "Your name" }),
5721
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5989
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
5990
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { className: "rk-label", htmlFor: "rk-author", children: "Your name" }),
5991
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5722
5992
  "input",
5723
5993
  {
5724
5994
  id: "rk-author",
@@ -5732,36 +6002,36 @@ function CoachMark() {
5732
6002
  }
5733
6003
  )
5734
6004
  ] }),
5735
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 4 }, children: [
5736
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => done(false), children: "Not now" }),
5737
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "rk-btn rk-btn-primary", onClick: () => done(true), children: "Start reviewing" })
6005
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 4 }, children: [
6006
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => done(false), children: "Not now" }),
6007
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "rk-btn rk-btn-primary", onClick: () => done(true), children: "Start reviewing" })
5738
6008
  ] })
5739
6009
  ] })
5740
6010
  ] });
5741
6011
  }
5742
6012
 
5743
6013
  // src/client/components/Confirm.tsx
5744
- var import_react8 = require("react");
5745
- var import_jsx_runtime7 = require("react/jsx-runtime");
6014
+ var import_react9 = require("react");
6015
+ var import_jsx_runtime8 = require("react/jsx-runtime");
5746
6016
  function ConfirmHost() {
5747
6017
  const { confirm } = useStore();
5748
6018
  const dispatch = useDispatch();
5749
- const cancelRef = (0, import_react8.useRef)(null);
5750
- (0, import_react8.useEffect)(() => {
6019
+ const cancelRef = (0, import_react9.useRef)(null);
6020
+ (0, import_react9.useEffect)(() => {
5751
6021
  if (confirm) cancelRef.current?.focus();
5752
6022
  }, [confirm]);
5753
6023
  if (!confirm) return null;
5754
6024
  const close = () => dispatch({ type: "SET_CONFIRM", confirm: null });
5755
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
5756
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rk-backdrop", onClick: close }),
5757
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rk-modal", role: "alertdialog", "aria-modal": "true", "aria-label": confirm.title, children: [
5758
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rk-modal-body", children: [
5759
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rk-modal-title", children: confirm.title }),
5760
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rk-modal-text", children: confirm.message })
6025
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
6026
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "rk-backdrop", onClick: close }),
6027
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "rk-modal", role: "alertdialog", "aria-modal": "true", "aria-label": confirm.title, children: [
6028
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "rk-modal-body", children: [
6029
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "rk-modal-title", children: confirm.title }),
6030
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "rk-modal-text", children: confirm.message })
5761
6031
  ] }),
5762
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rk-modal-actions", children: [
5763
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { ref: cancelRef, type: "button", className: "rk-btn rk-btn-ghost", onClick: close, children: "Cancel" }),
5764
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
6032
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "rk-modal-actions", children: [
6033
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { ref: cancelRef, type: "button", className: "rk-btn rk-btn-ghost", onClick: close, children: "Cancel" }),
6034
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
5765
6035
  "button",
5766
6036
  {
5767
6037
  type: "button",
@@ -5780,10 +6050,10 @@ function ConfirmHost() {
5780
6050
  }
5781
6051
 
5782
6052
  // src/client/components/ImageEditor.tsx
5783
- var import_react10 = require("react");
6053
+ var import_react11 = require("react");
5784
6054
 
5785
6055
  // src/client/useAnchored.ts
5786
- var import_react9 = require("react");
6056
+ var import_react10 = require("react");
5787
6057
 
5788
6058
  // src/client/position.ts
5789
6059
  function place(anchor, size, side, offset) {
@@ -5828,9 +6098,9 @@ function computePosition(anchor, size, options = {}) {
5828
6098
 
5829
6099
  // src/client/useAnchored.ts
5830
6100
  function useAnchored(anchorEl, side = "bottom", offset = 8) {
5831
- const ref = (0, import_react9.useRef)(null);
5832
- const [pos, setPos] = (0, import_react9.useState)(null);
5833
- (0, import_react9.useLayoutEffect)(() => {
6101
+ const ref = (0, import_react10.useRef)(null);
6102
+ const [pos, setPos] = (0, import_react10.useState)(null);
6103
+ (0, import_react10.useLayoutEffect)(() => {
5834
6104
  if (!anchorEl) {
5835
6105
  setPos(null);
5836
6106
  return;
@@ -5861,7 +6131,7 @@ function anchoredStyle(pos) {
5861
6131
  }
5862
6132
 
5863
6133
  // src/client/components/ImageEditor.tsx
5864
- var import_jsx_runtime8 = require("react/jsx-runtime");
6134
+ var import_jsx_runtime9 = require("react/jsx-runtime");
5865
6135
  var MAX_BYTES = 2 * 1024 * 1024;
5866
6136
  var FETCH_TIMEOUT_MS = 12e3;
5867
6137
  var PROBE_TIMEOUT_MS = 8e3;
@@ -5919,15 +6189,15 @@ function urlFromDataTransfer(dt) {
5919
6189
  function ImageEditor({ editor }) {
5920
6190
  const dispatch = useDispatch();
5921
6191
  const { route, session } = useStore();
5922
- const [replacement, setReplacement] = (0, import_react10.useState)(null);
5923
- const [urlValue, setUrlValue] = (0, import_react10.useState)("");
5924
- const [loading2, setLoading] = (0, import_react10.useState)(false);
5925
- const [description, setDescription] = (0, import_react10.useState)("");
5926
- const [priority, setPriority] = (0, import_react10.useState)("nice");
5927
- const [over, setOver] = (0, import_react10.useState)(false);
5928
- const fileInput = (0, import_react10.useRef)(null);
5929
- const abortRef = (0, import_react10.useRef)(null);
5930
- const debounceRef = (0, import_react10.useRef)(null);
6192
+ const [replacement, setReplacement] = (0, import_react11.useState)(null);
6193
+ const [urlValue, setUrlValue] = (0, import_react11.useState)("");
6194
+ const [loading2, setLoading] = (0, import_react11.useState)(false);
6195
+ const [description, setDescription] = (0, import_react11.useState)("");
6196
+ const [priority, setPriority] = (0, import_react11.useState)("nice");
6197
+ const [over, setOver] = (0, import_react11.useState)(false);
6198
+ const fileInput = (0, import_react11.useRef)(null);
6199
+ const abortRef = (0, import_react11.useRef)(null);
6200
+ const debounceRef = (0, import_react11.useRef)(null);
5931
6201
  const { ref, pos } = useAnchored(editor.element, "bottom", 12);
5932
6202
  const image = findImage(editor.element);
5933
6203
  const beforeSrc = image?.currentSrc || image?.src || "";
@@ -5991,7 +6261,7 @@ function ImageEditor({ editor }) {
5991
6261
  }
5992
6262
  }
5993
6263
  };
5994
- (0, import_react10.useEffect)(() => {
6264
+ (0, import_react11.useEffect)(() => {
5995
6265
  if (debounceRef.current !== null) window.clearTimeout(debounceRef.current);
5996
6266
  if (urlValue.trim() === "" || replacement !== null) return;
5997
6267
  debounceRef.current = window.setTimeout(() => {
@@ -6001,8 +6271,8 @@ function ImageEditor({ editor }) {
6001
6271
  if (debounceRef.current !== null) window.clearTimeout(debounceRef.current);
6002
6272
  };
6003
6273
  }, [urlValue, replacement]);
6004
- (0, import_react10.useEffect)(() => () => abortRef.current?.abort(), []);
6005
- (0, import_react10.useEffect)(() => {
6274
+ (0, import_react11.useEffect)(() => () => abortRef.current?.abort(), []);
6275
+ (0, import_react11.useEffect)(() => {
6006
6276
  const onPaste = (event) => {
6007
6277
  const data = event.clipboardData;
6008
6278
  if (!data) return;
@@ -6080,23 +6350,23 @@ function ImageEditor({ editor }) {
6080
6350
  toast(dispatch, "Image proposal saved", "success");
6081
6351
  };
6082
6352
  const previewSrc = replacement?.kind === "data" ? replacement.dataUrl : replacement?.url;
6083
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { ref, className: "rk-card", style: anchoredStyle(pos), role: "dialog", "aria-label": "Replace image", children: [
6084
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "rk-card-head", children: [
6085
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(IconImage, { size: 14 }),
6353
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { ref, className: "rk-card", style: anchoredStyle(pos), role: "dialog", "aria-label": "Replace image", children: [
6354
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rk-card-head", children: [
6355
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(IconImage, { size: 14 }),
6086
6356
  "Replace image"
6087
6357
  ] }),
6088
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "rk-card-body", children: [
6089
- replacement && previewSrc ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "rk-thumbs", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "rk-thumb-pair", title: "Hover to compare with the current image", children: [
6090
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("figure", { className: "rk-thumb", children: [
6091
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: beforeSrc, alt: "Current" }),
6092
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("figcaption", { children: "Before" })
6358
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rk-card-body", children: [
6359
+ replacement && previewSrc ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "rk-thumbs", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rk-thumb-pair", title: "Hover to compare with the current image", children: [
6360
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("figure", { className: "rk-thumb", children: [
6361
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("img", { src: beforeSrc, alt: "Current" }),
6362
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("figcaption", { children: "Before" })
6093
6363
  ] }),
6094
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("figure", { className: "rk-thumb", children: [
6095
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: previewSrc, alt: "Proposed replacement" }),
6096
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("figcaption", { children: replacement.kind === "url" ? "After \xB7 linked" : "After" })
6364
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("figure", { className: "rk-thumb", children: [
6365
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("img", { src: previewSrc, alt: "Proposed replacement" }),
6366
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("figcaption", { children: replacement.kind === "url" ? "After \xB7 linked" : "After" })
6097
6367
  ] })
6098
- ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
6099
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
6368
+ ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
6369
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
6100
6370
  "button",
6101
6371
  {
6102
6372
  type: "button",
@@ -6109,18 +6379,18 @@ function ImageEditor({ editor }) {
6109
6379
  onDragLeave: () => setOver(false),
6110
6380
  onDrop,
6111
6381
  children: [
6112
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(IconUpload, { size: 18 }),
6113
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
6382
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(IconUpload, { size: 18 }),
6383
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
6114
6384
  "Drop an image \u2014 from your disk or another tab \u2014",
6115
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("br", {}),
6385
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("br", {}),
6116
6386
  "or click to browse (max 2 MB)"
6117
6387
  ] })
6118
6388
  ]
6119
6389
  }
6120
6390
  ),
6121
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "rk-url", children: [
6122
- loading2 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "rk-spinner", "aria-label": "Loading image" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(IconLink, { size: 13 }),
6123
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
6391
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rk-url", children: [
6392
+ loading2 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "rk-spinner", "aria-label": "Loading image" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(IconLink, { size: 13 }),
6393
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6124
6394
  "input",
6125
6395
  {
6126
6396
  className: "rk-input",
@@ -6135,9 +6405,9 @@ function ImageEditor({ editor }) {
6135
6405
  )
6136
6406
  ] })
6137
6407
  ] }),
6138
- replacement && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: clearReplacement, children: "Remove replacement" }),
6139
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("input", { ref: fileInput, type: "file", accept: "image/*", hidden: true, onChange: onPick }),
6140
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
6408
+ replacement && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: clearReplacement, children: "Remove replacement" }),
6409
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("input", { ref: fileInput, type: "file", accept: "image/*", hidden: true, onChange: onPick }),
6410
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6141
6411
  "textarea",
6142
6412
  {
6143
6413
  className: "rk-textarea",
@@ -6147,43 +6417,43 @@ function ImageEditor({ editor }) {
6147
6417
  rows: 2
6148
6418
  }
6149
6419
  ),
6150
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "rk-seg", role: "group", "aria-label": "Priority", children: [
6151
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", "aria-pressed": priority === "must", onClick: () => setPriority("must"), children: "Must" }),
6152
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", "aria-pressed": priority === "nice", onClick: () => setPriority("nice"), children: "Nice to have" })
6420
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rk-seg", role: "group", "aria-label": "Priority", children: [
6421
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", "aria-pressed": priority === "must", onClick: () => setPriority("must"), children: "Must" }),
6422
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", "aria-pressed": priority === "nice", onClick: () => setPriority("nice"), children: "Nice to have" })
6153
6423
  ] })
6154
6424
  ] }),
6155
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "rk-card-foot", children: [
6156
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "rk-hint", children: [
6157
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "rk-kbd", children: "esc" }),
6425
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rk-card-foot", children: [
6426
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "rk-hint", children: [
6427
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "rk-kbd", children: "esc" }),
6158
6428
  " cancel"
6159
6429
  ] }),
6160
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "rk-actions", children: [
6161
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => dispatch({ type: "CLOSE_EDITOR" }), children: "Cancel" }),
6162
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "rk-btn rk-btn-primary", onClick: save, children: "Save" })
6430
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rk-actions", children: [
6431
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => dispatch({ type: "CLOSE_EDITOR" }), children: "Cancel" }),
6432
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "rk-btn rk-btn-primary", onClick: save, children: "Save" })
6163
6433
  ] })
6164
6434
  ] })
6165
6435
  ] });
6166
6436
  }
6167
6437
 
6168
6438
  // src/client/components/InlineEditor.tsx
6169
- var import_react11 = require("react");
6170
- var import_jsx_runtime9 = require("react/jsx-runtime");
6439
+ var import_react12 = require("react");
6440
+ var import_jsx_runtime10 = require("react/jsx-runtime");
6171
6441
  function InlineEditor({ editor }) {
6172
6442
  const dispatch = useDispatch();
6173
6443
  const { route, session } = useStore();
6174
- const [after, setAfter] = (0, import_react11.useState)(() => editor.element.innerText);
6175
- const [priority, setPriority] = (0, import_react11.useState)("nice");
6176
- const [note, setNote] = (0, import_react11.useState)("");
6177
- const original = (0, import_react11.useRef)(null);
6178
- const baseline = (0, import_react11.useRef)(null);
6179
- const [sizeDelta, setSizeDelta] = (0, import_react11.useState)(0);
6180
- const [widthDelta, setWidthDelta] = (0, import_react11.useState)(0);
6181
- const [clipped, setClipped] = (0, import_react11.useState)(false);
6182
- const ringRef = (0, import_react11.useRef)(null);
6183
- const afterRef = (0, import_react11.useRef)(null);
6444
+ const [after, setAfter] = (0, import_react12.useState)(() => editor.element.innerText);
6445
+ const [priority, setPriority] = (0, import_react12.useState)("nice");
6446
+ const [note, setNote] = (0, import_react12.useState)("");
6447
+ const original = (0, import_react12.useRef)(null);
6448
+ const baseline = (0, import_react12.useRef)(null);
6449
+ const [sizeDelta, setSizeDelta] = (0, import_react12.useState)(0);
6450
+ const [widthDelta, setWidthDelta] = (0, import_react12.useState)(0);
6451
+ const [clipped, setClipped] = (0, import_react12.useState)(false);
6452
+ const ringRef = (0, import_react12.useRef)(null);
6453
+ const afterRef = (0, import_react12.useRef)(null);
6184
6454
  const { ref, pos } = useAnchored(editor.element, "bottom", 12);
6185
6455
  const element = editor.element;
6186
- (0, import_react11.useEffect)(() => {
6456
+ (0, import_react12.useEffect)(() => {
6187
6457
  const el = element;
6188
6458
  original.current = { html: el.innerHTML, text: el.innerText, styleAttr: el.getAttribute("style") };
6189
6459
  const rect = el.getBoundingClientRect();
@@ -6213,8 +6483,8 @@ function InlineEditor({ editor }) {
6213
6483
  }
6214
6484
  };
6215
6485
  }, [element]);
6216
- const focusedOnce = (0, import_react11.useRef)(false);
6217
- (0, import_react11.useEffect)(() => {
6486
+ const focusedOnce = (0, import_react12.useRef)(false);
6487
+ (0, import_react12.useEffect)(() => {
6218
6488
  const field = afterRef.current;
6219
6489
  if (pos === null || focusedOnce.current || !field) return;
6220
6490
  focusedOnce.current = true;
@@ -6222,7 +6492,7 @@ function InlineEditor({ editor }) {
6222
6492
  const end = field.value.length;
6223
6493
  field.setSelectionRange(end, end);
6224
6494
  }, [pos]);
6225
- (0, import_react11.useEffect)(() => {
6495
+ (0, import_react12.useEffect)(() => {
6226
6496
  const field = afterRef.current;
6227
6497
  if (!field) return;
6228
6498
  field.style.height = "auto";
@@ -6232,7 +6502,7 @@ function InlineEditor({ editor }) {
6232
6502
  setAfter(value);
6233
6503
  if (element.innerText !== value) setElementText(element, value);
6234
6504
  };
6235
- (0, import_react11.useEffect)(() => {
6505
+ (0, import_react12.useEffect)(() => {
6236
6506
  let raf = 0;
6237
6507
  let lastDelta = 0;
6238
6508
  let lastWidth = 0;
@@ -6302,7 +6572,7 @@ function InlineEditor({ editor }) {
6302
6572
  cancel();
6303
6573
  }
6304
6574
  };
6305
- (0, import_react11.useEffect)(() => {
6575
+ (0, import_react12.useEffect)(() => {
6306
6576
  const el = element;
6307
6577
  const onKeyDown = (event) => {
6308
6578
  if (event.key === "Enter" && !event.shiftKey) {
@@ -6320,19 +6590,19 @@ function InlineEditor({ editor }) {
6320
6590
  });
6321
6591
  const before = original.current?.text ?? "";
6322
6592
  const delta = after.length - before.length;
6323
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
6324
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { ref: ringRef, className: "rk-edit-ring" }),
6325
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { ref, className: "rk-card", style: anchoredStyle(pos), role: "dialog", "aria-label": "Rewrite text", children: [
6326
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rk-card-head", children: [
6327
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(IconText, { size: 14 }),
6593
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
6594
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { ref: ringRef, className: "rk-edit-ring" }),
6595
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { ref, className: "rk-card", style: anchoredStyle(pos), role: "dialog", "aria-label": "Rewrite text", children: [
6596
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "rk-card-head", children: [
6597
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(IconText, { size: 14 }),
6328
6598
  "Rewrite text",
6329
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "rk-spacer" }),
6330
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "rk-delta", "aria-live": "polite", children: [
6599
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "rk-spacer" }),
6600
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "rk-delta", "aria-live": "polite", children: [
6331
6601
  before.length,
6332
6602
  " \u2192 ",
6333
6603
  after.length,
6334
6604
  " ",
6335
- delta !== 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: delta > 0 ? "rk-plus" : "rk-minus", children: [
6605
+ delta !== 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: delta > 0 ? "rk-plus" : "rk-minus", children: [
6336
6606
  "(",
6337
6607
  delta > 0 ? "+" : "",
6338
6608
  delta,
@@ -6340,15 +6610,15 @@ function InlineEditor({ editor }) {
6340
6610
  ] })
6341
6611
  ] })
6342
6612
  ] }),
6343
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rk-card-body", children: [
6344
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rk-diff", "aria-label": "Before and after", children: [
6345
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rk-diff-row rk-diff-before", children: [
6346
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "rk-diff-tag", children: "Before" }),
6347
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "rk-diff-text", children: before })
6613
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "rk-card-body", children: [
6614
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "rk-diff", "aria-label": "Before and after", children: [
6615
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "rk-diff-row rk-diff-before", children: [
6616
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "rk-diff-tag", children: "Before" }),
6617
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "rk-diff-text", children: before })
6348
6618
  ] }),
6349
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rk-diff-row rk-diff-after", children: [
6350
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "rk-diff-tag", children: "After" }),
6351
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6619
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "rk-diff-row rk-diff-after", children: [
6620
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "rk-diff-tag", children: "After" }),
6621
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6352
6622
  "textarea",
6353
6623
  {
6354
6624
  ref: afterRef,
@@ -6362,11 +6632,11 @@ function InlineEditor({ editor }) {
6362
6632
  )
6363
6633
  ] })
6364
6634
  ] }),
6365
- (Math.abs(sizeDelta) > 2 || Math.abs(widthDelta) > 4 || clipped) && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rk-size-note", role: "status", children: [
6635
+ (Math.abs(sizeDelta) > 2 || Math.abs(widthDelta) > 4 || clipped) && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "rk-size-note", role: "status", children: [
6366
6636
  Math.abs(sizeDelta) > 2 && (() => {
6367
6637
  const lines = baseline.current ? Math.round(Math.abs(sizeDelta) / baseline.current.lineHeight) : 0;
6368
6638
  const lineNote = lines >= 1 ? ` (~${lines} line${lines === 1 ? "" : "s"})` : "";
6369
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
6639
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
6370
6640
  "Block ",
6371
6641
  sizeDelta > 0 ? "grew" : "shrank",
6372
6642
  " by ",
@@ -6376,16 +6646,16 @@ function InlineEditor({ editor }) {
6376
6646
  " \u2014 as it would with this text live."
6377
6647
  ] });
6378
6648
  })(),
6379
- Math.abs(sizeDelta) <= 2 && Math.abs(widthDelta) > 4 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
6649
+ Math.abs(sizeDelta) <= 2 && Math.abs(widthDelta) > 4 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
6380
6650
  "Block got ",
6381
6651
  widthDelta > 0 ? "wider" : "narrower",
6382
6652
  " by ",
6383
6653
  Math.abs(widthDelta),
6384
6654
  " px \u2014 as it would with this text live."
6385
6655
  ] }),
6386
- clipped && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "rk-clip-note", children: "Text exceeds the visible crop here and will be cut off." })
6656
+ clipped && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "rk-clip-note", children: "Text exceeds the visible crop here and will be cut off." })
6387
6657
  ] }),
6388
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
6658
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6389
6659
  "input",
6390
6660
  {
6391
6661
  className: "rk-input",
@@ -6394,24 +6664,24 @@ function InlineEditor({ editor }) {
6394
6664
  onChange: (event) => setNote(event.target.value)
6395
6665
  }
6396
6666
  ),
6397
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rk-seg", role: "group", "aria-label": "Priority", children: [
6398
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", "aria-pressed": priority === "must", onClick: () => setPriority("must"), children: "Must" }),
6399
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", "aria-pressed": priority === "nice", onClick: () => setPriority("nice"), children: "Nice to have" })
6667
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "rk-seg", role: "group", "aria-label": "Priority", children: [
6668
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", "aria-pressed": priority === "must", onClick: () => setPriority("must"), children: "Must" }),
6669
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", "aria-pressed": priority === "nice", onClick: () => setPriority("nice"), children: "Nice to have" })
6400
6670
  ] })
6401
6671
  ] }),
6402
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rk-card-foot", children: [
6403
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "rk-hint", children: [
6404
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "rk-kbd", children: "\u21B5" }),
6672
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "rk-card-foot", children: [
6673
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "rk-hint", children: [
6674
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "rk-kbd", children: "\u21B5" }),
6405
6675
  " save \xB7 ",
6406
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "rk-kbd", children: "\u21E7\u21B5" }),
6676
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "rk-kbd", children: "\u21E7\u21B5" }),
6407
6677
  " newline \xB7",
6408
6678
  " ",
6409
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "rk-kbd", children: "esc" }),
6679
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "rk-kbd", children: "esc" }),
6410
6680
  " cancel"
6411
6681
  ] }),
6412
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rk-actions", children: [
6413
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: cancel, children: "Cancel" }),
6414
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", className: "rk-btn rk-btn-primary", onClick: commit, children: "Save" })
6682
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "rk-actions", children: [
6683
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: cancel, children: "Cancel" }),
6684
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "rk-btn rk-btn-primary", onClick: commit, children: "Save" })
6415
6685
  ] })
6416
6686
  ] })
6417
6687
  ] })
@@ -6419,8 +6689,8 @@ function InlineEditor({ editor }) {
6419
6689
  }
6420
6690
 
6421
6691
  // src/client/components/NoteComposer.tsx
6422
- var import_react12 = require("react");
6423
- var import_jsx_runtime10 = require("react/jsx-runtime");
6692
+ var import_react13 = require("react");
6693
+ var import_jsx_runtime11 = require("react/jsx-runtime");
6424
6694
  var TITLES = {
6425
6695
  comment: "Comment",
6426
6696
  delete: "Mark for deletion",
@@ -6436,14 +6706,14 @@ var PLACEHOLDERS = {
6436
6706
  function NoteComposer({ editor }) {
6437
6707
  const dispatch = useDispatch();
6438
6708
  const { route, session } = useStore();
6439
- const [text, setText] = (0, import_react12.useState)("");
6440
- const [direction, setDirection] = (0, import_react12.useState)(void 0);
6441
- const [priority, setPriority] = (0, import_react12.useState)("nice");
6442
- const textareaRef = (0, import_react12.useRef)(null);
6709
+ const [text, setText] = (0, import_react13.useState)("");
6710
+ const [direction, setDirection] = (0, import_react13.useState)(void 0);
6711
+ const [priority, setPriority] = (0, import_react13.useState)("nice");
6712
+ const textareaRef = (0, import_react13.useRef)(null);
6443
6713
  const { ref, pos } = useAnchored(editor.element, "bottom", 12);
6444
6714
  const { kind } = editor;
6445
- const focusedOnce = (0, import_react12.useRef)(false);
6446
- (0, import_react12.useEffect)(() => {
6715
+ const focusedOnce = (0, import_react13.useRef)(false);
6716
+ (0, import_react13.useEffect)(() => {
6447
6717
  const field = textareaRef.current;
6448
6718
  if (pos === null || focusedOnce.current || !field) return;
6449
6719
  focusedOnce.current = true;
@@ -6502,13 +6772,13 @@ function NoteComposer({ editor }) {
6502
6772
  save();
6503
6773
  }
6504
6774
  };
6505
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { ref, className: "rk-card", style: anchoredStyle(pos), role: "dialog", "aria-label": TITLES[kind], children: [
6506
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "rk-card-head", children: [
6775
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { ref, className: "rk-card", style: anchoredStyle(pos), role: "dialog", "aria-label": TITLES[kind], children: [
6776
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-card-head", children: [
6507
6777
  iconForType(kind),
6508
6778
  TITLES[kind]
6509
6779
  ] }),
6510
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "rk-card-body", children: [
6511
- kind === "move" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "rk-seg", role: "group", "aria-label": "Direction", children: ["up", "down", "first", "last"].map((option) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6780
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-card-body", children: [
6781
+ kind === "move" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rk-seg", role: "group", "aria-label": "Direction", children: ["up", "down", "first", "last"].map((option) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
6512
6782
  "button",
6513
6783
  {
6514
6784
  type: "button",
@@ -6518,7 +6788,7 @@ function NoteComposer({ editor }) {
6518
6788
  },
6519
6789
  option
6520
6790
  )) }),
6521
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
6791
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
6522
6792
  "textarea",
6523
6793
  {
6524
6794
  ref: textareaRef,
@@ -6530,29 +6800,29 @@ function NoteComposer({ editor }) {
6530
6800
  rows: 3
6531
6801
  }
6532
6802
  ),
6533
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "rk-seg", role: "group", "aria-label": "Priority", children: [
6534
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", "aria-pressed": priority === "must", onClick: () => setPriority("must"), children: "Must" }),
6535
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", "aria-pressed": priority === "nice", onClick: () => setPriority("nice"), children: "Nice to have" })
6803
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-seg", role: "group", "aria-label": "Priority", children: [
6804
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", "aria-pressed": priority === "must", onClick: () => setPriority("must"), children: "Must" }),
6805
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", "aria-pressed": priority === "nice", onClick: () => setPriority("nice"), children: "Nice to have" })
6536
6806
  ] })
6537
6807
  ] }),
6538
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "rk-card-foot", children: [
6539
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "rk-hint", children: [
6540
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "rk-kbd", children: "\u2318\u21B5" }),
6808
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-card-foot", children: [
6809
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "rk-hint", children: [
6810
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-kbd", children: "\u2318\u21B5" }),
6541
6811
  " save \xB7 ",
6542
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "rk-kbd", children: "esc" }),
6812
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-kbd", children: "esc" }),
6543
6813
  " cancel"
6544
6814
  ] }),
6545
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "rk-actions", children: [
6546
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => dispatch({ type: "CLOSE_EDITOR" }), children: "Cancel" }),
6547
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "rk-btn rk-btn-primary", onClick: save, children: "Save" })
6815
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-actions", children: [
6816
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => dispatch({ type: "CLOSE_EDITOR" }), children: "Cancel" }),
6817
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: "rk-btn rk-btn-primary", onClick: save, children: "Save" })
6548
6818
  ] })
6549
6819
  ] })
6550
6820
  ] });
6551
6821
  }
6552
6822
 
6553
6823
  // src/client/components/Panel.tsx
6554
- var import_react13 = require("react");
6555
- var import_jsx_runtime11 = require("react/jsx-runtime");
6824
+ var import_react14 = require("react");
6825
+ var import_jsx_runtime12 = require("react/jsx-runtime");
6556
6826
  function changeSummary(item) {
6557
6827
  if (isCopyItem(item)) return `\u201C${item.payload.after}\u201D`;
6558
6828
  if (isImageItem(item)) {
@@ -6634,24 +6904,24 @@ function formatBytes(bytes) {
6634
6904
  function CopyDiff({ before, after }) {
6635
6905
  const parts = diffParts(before, after);
6636
6906
  const delta = after.length - before.length;
6637
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-diffview", children: [
6638
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-diff-row rk-diff-before", children: [
6639
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-diff-tag", children: "Before" }),
6640
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "rk-diff-text", children: [
6907
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-diffview", children: [
6908
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-diff-row rk-diff-before", children: [
6909
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-diff-tag", children: "Before" }),
6910
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "rk-diff-text", children: [
6641
6911
  parts.prefix,
6642
- parts.delMid && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("mark", { className: "rk-del", children: parts.delMid }),
6912
+ parts.delMid && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("mark", { className: "rk-del", children: parts.delMid }),
6643
6913
  parts.suffix
6644
6914
  ] })
6645
6915
  ] }),
6646
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-diff-row rk-diff-after", children: [
6647
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-diff-tag", children: "After" }),
6648
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "rk-diff-text", children: [
6916
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-diff-row rk-diff-after", children: [
6917
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-diff-tag", children: "After" }),
6918
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "rk-diff-text", children: [
6649
6919
  parts.prefix,
6650
- parts.insMid && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("mark", { className: "rk-ins", children: parts.insMid }),
6920
+ parts.insMid && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("mark", { className: "rk-ins", children: parts.insMid }),
6651
6921
  parts.suffix
6652
6922
  ] })
6653
6923
  ] }),
6654
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-diff-len", children: [
6924
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-diff-len", children: [
6655
6925
  before.length,
6656
6926
  " \u2192 ",
6657
6927
  after.length,
@@ -6662,8 +6932,8 @@ function CopyDiff({ before, after }) {
6662
6932
  }
6663
6933
  function ImageDetail({ beforeSrc, afterSrc, afterDataUrl }) {
6664
6934
  const replacement = afterDataUrl ?? afterSrc;
6665
- const [dims, setDims] = (0, import_react13.useState)("");
6666
- (0, import_react13.useEffect)(() => {
6935
+ const [dims, setDims] = (0, import_react14.useState)("");
6936
+ (0, import_react14.useEffect)(() => {
6667
6937
  if (!replacement) return;
6668
6938
  const img = new Image();
6669
6939
  img.onload = () => setDims(`${img.naturalWidth}\xD7${img.naturalHeight}px`);
@@ -6688,55 +6958,67 @@ function ImageDetail({ beforeSrc, afterSrc, afterDataUrl }) {
6688
6958
  window.open(replacement, "_blank", "noopener");
6689
6959
  }
6690
6960
  };
6691
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-imgdetail", children: [
6692
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-thumbs", children: [
6693
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { className: "rk-thumb", children: [
6694
- beforeSrc ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("img", { src: beforeSrc, alt: "Current" }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rk-thumb-missing", children: "no image" }),
6695
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { children: "Before" })
6961
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-imgdetail", children: [
6962
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-thumbs", children: [
6963
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "rk-thumb", children: [
6964
+ beforeSrc ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("img", { src: beforeSrc, alt: "Current" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rk-thumb-missing", children: "no image" }),
6965
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("figcaption", { children: "Before" })
6696
6966
  ] }),
6697
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { className: "rk-thumb", children: [
6698
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("img", { src: replacement, alt: "Proposed replacement" }),
6699
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figcaption", { children: [
6967
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "rk-thumb", children: [
6968
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("img", { src: replacement, alt: "Proposed replacement" }),
6969
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figcaption", { children: [
6700
6970
  "After",
6701
6971
  embedded ? "" : " \xB7 linked"
6702
6972
  ] })
6703
6973
  ] })
6704
6974
  ] }),
6705
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-imgdetail-meta", children: [
6975
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-imgdetail-meta", children: [
6706
6976
  embedded ? "Embedded image" : "Linked image",
6707
6977
  dims ? ` \xB7 ${dims}` : "",
6708
6978
  bytes !== null ? ` \xB7 ${formatBytes(bytes)}` : ""
6709
6979
  ] }),
6710
- !embedded && afterSrc && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("a", { className: "rk-imgdetail-url", href: afterSrc, target: "_blank", rel: "noopener noreferrer", title: afterSrc, children: afterSrc }),
6711
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-imgdetail-actions", children: [
6712
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => void download(), children: [
6713
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconDownload, { size: 12 }),
6980
+ !embedded && afterSrc && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("a", { className: "rk-imgdetail-url", href: afterSrc, target: "_blank", rel: "noopener noreferrer", title: afterSrc, children: afterSrc }),
6981
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-imgdetail-actions", children: [
6982
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => void download(), children: [
6983
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconDownload, { size: 12 }),
6714
6984
  " Download"
6715
6985
  ] }),
6716
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => window.open(replacement, "_blank", "noopener"), children: "Open" })
6986
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => window.open(replacement, "_blank", "noopener"), children: "Open" })
6717
6987
  ] })
6718
6988
  ] });
6719
6989
  }
6990
+ var SYNC_REASON_LABEL = {
6991
+ "token-short": "token too short",
6992
+ "bad-token": "token rejected",
6993
+ "room-full": "room full",
6994
+ unreachable: "server unreachable"
6995
+ };
6996
+ var SYNC_REASON_HELP = {
6997
+ "token-short": "The sync server needs a token of at least 8 characters. Use a longer `token` (or `syncToken`) in <ReviewKit /> and everyone with the link shares items live.",
6998
+ "bad-token": "The sync server rejected the token. Check `syncToken` / `token` in <ReviewKit />.",
6999
+ "room-full": "This page already has the maximum number of reviewers online (16). Try again later.",
7000
+ unreachable: "Could not reach the sync server. Usually the page's Content-Security-Policy is missing the server in connect-src, or the server is down. Items stay on this device meanwhile."
7001
+ };
6720
7002
  function Panel() {
6721
- const { session, panelOpen, activeItemId, badgeCorner, undoStack, preview, unapplied, syncStatus, emblemaPush, peers } = useStore();
7003
+ const { session, panelOpen, activeItemId, badgeCorner, undoStack, preview, unapplied, syncStatus, syncReason, emblemaPush, peers } = useStore();
6722
7004
  const dispatch = useDispatch();
6723
7005
  const config = useConfig();
6724
- const [sticky, setStickyState] = (0, import_react13.useState)(() => isStickyEnabled(config.token));
6725
- const [query, setQuery] = (0, import_react13.useState)("");
6726
- const [typeFilter, setTypeFilter] = (0, import_react13.useState)(/* @__PURE__ */ new Set());
6727
- const [prioFilter, setPrioFilter] = (0, import_react13.useState)(/* @__PURE__ */ new Set());
6728
- const [statusFilter, setStatusFilter] = (0, import_react13.useState)(/* @__PURE__ */ new Set());
6729
- const [filterOpen, setFilterOpen] = (0, import_react13.useState)(false);
6730
- const [settingsOpen, setSettingsOpen] = (0, import_react13.useState)(false);
6731
- const [emblemaAlive, setEmblemaAlive] = (0, import_react13.useState)(null);
6732
- const [emblemaSending, setEmblemaSending] = (0, import_react13.useState)(false);
6733
- const [emblemaSync, setEmblemaSync] = (0, import_react13.useState)("unknown");
6734
- const panelRef = (0, import_react13.useRef)(null);
6735
- const searchRef = (0, import_react13.useRef)(null);
6736
- const importRef = (0, import_react13.useRef)(null);
6737
- const sheetDrag = (0, import_react13.useRef)(null);
6738
- const items = (0, import_react13.useMemo)(() => session?.items ?? [], [session]);
6739
- const { orphanIds, hiddenIds } = (0, import_react13.useMemo)(() => {
7006
+ const [sticky, setStickyState] = (0, import_react14.useState)(() => isStickyEnabled(config.token));
7007
+ const [query, setQuery] = (0, import_react14.useState)("");
7008
+ const [typeFilter, setTypeFilter] = (0, import_react14.useState)(/* @__PURE__ */ new Set());
7009
+ const [prioFilter, setPrioFilter] = (0, import_react14.useState)(/* @__PURE__ */ new Set());
7010
+ const [statusFilter, setStatusFilter] = (0, import_react14.useState)(/* @__PURE__ */ new Set());
7011
+ const [filterOpen, setFilterOpen] = (0, import_react14.useState)(false);
7012
+ const [settingsOpen, setSettingsOpen] = (0, import_react14.useState)(false);
7013
+ const [emblemaAlive, setEmblemaAlive] = (0, import_react14.useState)(null);
7014
+ const [emblemaSending, setEmblemaSending] = (0, import_react14.useState)(false);
7015
+ const [emblemaSync, setEmblemaSync] = (0, import_react14.useState)("unknown");
7016
+ const panelRef = (0, import_react14.useRef)(null);
7017
+ const searchRef = (0, import_react14.useRef)(null);
7018
+ const importRef = (0, import_react14.useRef)(null);
7019
+ const sheetDrag = (0, import_react14.useRef)(null);
7020
+ const items = (0, import_react14.useMemo)(() => session?.items ?? [], [session]);
7021
+ const { orphanIds, hiddenIds } = (0, import_react14.useMemo)(() => {
6740
7022
  const orphans = /* @__PURE__ */ new Set();
6741
7023
  const hiddens = /* @__PURE__ */ new Set();
6742
7024
  for (const item of items) {
@@ -6747,15 +7029,15 @@ function Panel() {
6747
7029
  }
6748
7030
  return { orphanIds: orphans, hiddenIds: hiddens };
6749
7031
  }, [items, panelOpen]);
6750
- const activeFilter = (0, import_react13.useMemo)(
7032
+ const activeFilter = (0, import_react14.useMemo)(
6751
7033
  () => typeFilter.size + prioFilter.size + statusFilter.size > 0 ? { types: [...typeFilter], priorities: [...prioFilter], statuses: [...statusFilter] } : null,
6752
7034
  [typeFilter, prioFilter, statusFilter]
6753
7035
  );
6754
- (0, import_react13.useEffect)(() => {
7036
+ (0, import_react14.useEffect)(() => {
6755
7037
  dispatch({ type: "SET_FILTER", filter: activeFilter });
6756
7038
  return () => dispatch({ type: "SET_FILTER", filter: null });
6757
7039
  }, [activeFilter, dispatch]);
6758
- const filtered = (0, import_react13.useMemo)(() => {
7040
+ const filtered = (0, import_react14.useMemo)(() => {
6759
7041
  const q = query.trim().toLowerCase();
6760
7042
  return items.map((item, index) => ({ item, number: index + 1 })).filter(({ item }) => {
6761
7043
  if (!itemMatchesFilter(item, activeFilter)) return false;
@@ -6764,22 +7046,22 @@ function Panel() {
6764
7046
  return haystack.includes(q);
6765
7047
  });
6766
7048
  }, [items, query, activeFilter]);
6767
- const typeCounts = (0, import_react13.useMemo)(() => {
7049
+ const typeCounts = (0, import_react14.useMemo)(() => {
6768
7050
  const counts = /* @__PURE__ */ new Map();
6769
7051
  for (const item of items) counts.set(item.type, (counts.get(item.type) ?? 0) + 1);
6770
7052
  return counts;
6771
7053
  }, [items]);
6772
- const prioCounts = (0, import_react13.useMemo)(() => {
7054
+ const prioCounts = (0, import_react14.useMemo)(() => {
6773
7055
  const counts = /* @__PURE__ */ new Map();
6774
7056
  for (const item of items) counts.set(item.priority, (counts.get(item.priority) ?? 0) + 1);
6775
7057
  return counts;
6776
7058
  }, [items]);
6777
- const statusCounts = (0, import_react13.useMemo)(() => {
7059
+ const statusCounts = (0, import_react14.useMemo)(() => {
6778
7060
  const counts = /* @__PURE__ */ new Map();
6779
7061
  for (const item of items) counts.set(item.status, (counts.get(item.status) ?? 0) + 1);
6780
7062
  return counts;
6781
7063
  }, [items]);
6782
- const groups = (0, import_react13.useMemo)(() => {
7064
+ const groups = (0, import_react14.useMemo)(() => {
6783
7065
  const map = /* @__PURE__ */ new Map();
6784
7066
  for (const entry of filtered) {
6785
7067
  const list = map.get(entry.item.route) ?? [];
@@ -6788,7 +7070,7 @@ function Panel() {
6788
7070
  }
6789
7071
  return Array.from(map.entries());
6790
7072
  }, [filtered]);
6791
- (0, import_react13.useEffect)(() => {
7073
+ (0, import_react14.useEffect)(() => {
6792
7074
  if (!filterOpen && !settingsOpen) return;
6793
7075
  const onDown = (event) => {
6794
7076
  const path = event.composedPath();
@@ -6799,7 +7081,7 @@ function Panel() {
6799
7081
  document.addEventListener("pointerdown", onDown, true);
6800
7082
  return () => document.removeEventListener("pointerdown", onDown, true);
6801
7083
  }, [filterOpen, settingsOpen]);
6802
- (0, import_react13.useEffect)(() => {
7084
+ (0, import_react14.useEffect)(() => {
6803
7085
  if (!settingsOpen) return;
6804
7086
  let alive = true;
6805
7087
  const probe = () => {
@@ -6823,7 +7105,7 @@ function Panel() {
6823
7105
  live: "paired",
6824
7106
  off: "not paired"
6825
7107
  }[emblemaPairState];
6826
- (0, import_react13.useEffect)(() => {
7108
+ (0, import_react14.useEffect)(() => {
6827
7109
  if (!panelOpen || activeItemId === null) return;
6828
7110
  const row = panelRef.current?.querySelector(`[data-item-id="${activeItemId}"]`);
6829
7111
  row?.scrollIntoView({ block: "nearest" });
@@ -6939,7 +7221,7 @@ function Panel() {
6939
7221
  panel.style.transform = "";
6940
7222
  if (dy > 110) dispatch({ type: "SET_PANEL", open: false });
6941
7223
  };
6942
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
7224
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
6943
7225
  "div",
6944
7226
  {
6945
7227
  ref: panelRef,
@@ -6949,7 +7231,7 @@ function Panel() {
6949
7231
  "aria-label": "Review feedback panel",
6950
7232
  onKeyDown,
6951
7233
  children: [
6952
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7234
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
6953
7235
  "div",
6954
7236
  {
6955
7237
  className: "rk-sheet-handle",
@@ -6960,34 +7242,41 @@ function Panel() {
6960
7242
  onPointerCancel: onSheetHandleUp
6961
7243
  }
6962
7244
  ),
6963
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-panel-head", children: [
6964
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
6965
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rk-panel-title", children: "Feedback" }),
6966
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-panel-sub", children: [
7245
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-panel-head", children: [
7246
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
7247
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rk-panel-title", children: "Feedback" }),
7248
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-panel-sub", children: [
6967
7249
  items.length,
6968
7250
  " item",
6969
7251
  items.length === 1 ? "" : "s",
6970
7252
  session.author ? ` \xB7 ${session.author}` : "",
6971
- syncStatus !== "disabled" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: `rk-sync rk-sync-${syncStatus}`, children: [
6972
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-sync-dot" }),
6973
- syncStatus === "online" ? `live${peers.length > 0 ? ` \xB7 ${peers.length} other${peers.length === 1 ? "" : "s"}` : ""}` : syncStatus === "connecting" ? "connecting\u2026" : "local only"
6974
- ] }),
6975
- config.emblema && emblemaPush !== "idle" && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
7253
+ syncStatus !== "disabled" && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
7254
+ "span",
7255
+ {
7256
+ className: `rk-sync rk-sync-${syncStatus}${syncReason ? ` rk-sync-reason-${syncReason}` : ""}`,
7257
+ title: syncStatus === "online" ? "Live: everyone with this review link shares these items in real time" : syncStatus === "connecting" ? "Connecting to the sync server\u2026" : SYNC_REASON_HELP[syncReason ?? "unreachable"],
7258
+ children: [
7259
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-sync-dot" }),
7260
+ syncStatus === "online" ? `live${peers.length > 0 ? ` \xB7 ${peers.length} other${peers.length === 1 ? "" : "s"}` : ""}` : syncStatus === "connecting" ? "connecting\u2026" : `local only \xB7 ${SYNC_REASON_LABEL[syncReason ?? "unreachable"]}`
7261
+ ]
7262
+ }
7263
+ ),
7264
+ config.emblema && emblemaPush !== "idle" && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
6976
7265
  "span",
6977
7266
  {
6978
7267
  className: `rk-sync rk-epush-${emblemaPush}`,
6979
7268
  title: emblemaPush === "ok" ? "Review items stream to Emblema automatically" : emblemaPush === "sending" ? "Sending items to Emblema\u2026" : "Emblema isn't reachable \u2014 items resend on the next change, or use Resend all in the menu",
6980
7269
  children: [
6981
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-sync-dot" }),
7270
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-sync-dot" }),
6982
7271
  emblemaPush === "ok" ? "emblema" : emblemaPush === "sending" ? "emblema\u2026" : emblemaPush === "rejected" ? "emblema rejected" : "emblema offline"
6983
7272
  ]
6984
7273
  }
6985
7274
  )
6986
7275
  ] })
6987
7276
  ] }),
6988
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-spacer" }),
6989
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-menu-scope rk-menu-anchor", children: [
6990
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7277
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-spacer" }),
7278
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-menu-scope rk-menu-anchor", children: [
7279
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
6991
7280
  "button",
6992
7281
  {
6993
7282
  type: "button",
@@ -7003,11 +7292,11 @@ function Panel() {
7003
7292
  void pingEmblema(config.emblema).then((port) => setEmblemaAlive(port !== null));
7004
7293
  }
7005
7294
  },
7006
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconGear, { size: 15 })
7295
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconGear, { size: 15 })
7007
7296
  }
7008
7297
  ),
7009
- settingsOpen && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-menu", role: "menu", "aria-label": "Session settings", children: [
7010
- config.token && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
7298
+ settingsOpen && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-menu", role: "menu", "aria-label": "Session settings", children: [
7299
+ config.token && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
7011
7300
  "button",
7012
7301
  {
7013
7302
  type: "button",
@@ -7022,12 +7311,12 @@ function Panel() {
7022
7311
  toast(dispatch, next ? "Review stays on in this tab" : "Review active only via the link", "info");
7023
7312
  },
7024
7313
  children: [
7025
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: `rk-menu-check${sticky ? " rk-on" : ""}` }),
7314
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: `rk-menu-check${sticky ? " rk-on" : ""}` }),
7026
7315
  "Keep review on in this tab"
7027
7316
  ]
7028
7317
  }
7029
7318
  ),
7030
- config.emblema && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
7319
+ config.emblema && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
7031
7320
  "button",
7032
7321
  {
7033
7322
  type: "button",
@@ -7057,7 +7346,7 @@ function Panel() {
7057
7346
  }).finally(() => setEmblemaSending(false));
7058
7347
  },
7059
7348
  children: [
7060
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7349
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7061
7350
  "span",
7062
7351
  {
7063
7352
  className: "rk-emblema-dot",
@@ -7068,25 +7357,40 @@ function Panel() {
7068
7357
  ]
7069
7358
  }
7070
7359
  ),
7071
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
7360
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
7072
7361
  "button",
7073
7362
  {
7074
7363
  type: "button",
7075
7364
  role: "menuitem",
7076
7365
  className: "rk-menu-item",
7077
- title: emblemaPairState === "stale" ? "The local sync agent is not answering \u2014 run `npx @satanwagen/reviewkit-cli serve` in the project" : "Pair this project with the Emblema app via a sync key",
7366
+ title: emblemaPairState === "stale" ? "The local sync agent is not answering \u2014 run `npx -p @satanwagen/reviewkit-cli reviewkit-emblema serve` in the project" : "Pair this project with the Emblema app via a sync key",
7078
7367
  onClick: () => {
7079
7368
  setSettingsOpen(false);
7080
7369
  dispatch({ type: "SET_EMBLEMA_CONNECT", open: true });
7081
7370
  },
7082
7371
  children: [
7083
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-emblema-dot", "data-state": emblemaPairState }),
7372
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-emblema-dot", "data-state": emblemaPairState }),
7084
7373
  "Connect to Emblema\u2026",
7085
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-menu-count", children: emblemaPairLabel })
7374
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-menu-count", children: emblemaPairLabel })
7086
7375
  ]
7087
7376
  }
7088
7377
  ),
7089
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7378
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7379
+ "button",
7380
+ {
7381
+ type: "button",
7382
+ role: "menuitem",
7383
+ className: "rk-menu-item",
7384
+ title: resolveSyncToken(config) ? "Pair the reviewkit terminal CLI / Claude Code MCP bridge with this site" : "Needs live sync (sync is off on this page) \u2014 the terminal reads the shared room",
7385
+ disabled: !resolveSyncToken(config),
7386
+ onClick: () => {
7387
+ setSettingsOpen(false);
7388
+ dispatch({ type: "SET_TERMINAL_CONNECT", open: true });
7389
+ },
7390
+ children: "Connect terminal / agent\u2026"
7391
+ }
7392
+ ),
7393
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7090
7394
  "button",
7091
7395
  {
7092
7396
  type: "button",
@@ -7099,31 +7403,31 @@ function Panel() {
7099
7403
  )
7100
7404
  ] })
7101
7405
  ] }),
7102
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7406
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7103
7407
  "button",
7104
7408
  {
7105
7409
  type: "button",
7106
7410
  className: "rk-icon-btn",
7107
7411
  "aria-label": "Keyboard shortcuts",
7108
7412
  onClick: () => dispatch({ type: "SET_CHEATSHEET", open: true }),
7109
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconKeyboard, { size: 15 })
7413
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconKeyboard, { size: 15 })
7110
7414
  }
7111
7415
  ),
7112
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7416
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7113
7417
  "button",
7114
7418
  {
7115
7419
  type: "button",
7116
7420
  className: "rk-icon-btn",
7117
7421
  "aria-label": "Close panel",
7118
7422
  onClick: () => dispatch({ type: "SET_PANEL", open: false }),
7119
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconClose, { size: 14 })
7423
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconClose, { size: 14 })
7120
7424
  }
7121
7425
  )
7122
7426
  ] }),
7123
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-view-row", children: [
7124
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rk-view-info", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rk-view-title", children: "Page view" }) }),
7125
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-seg rk-view-toggle", role: "group", "aria-label": "Page view (P to toggle)", children: [
7126
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7427
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-view-row", children: [
7428
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rk-view-info", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rk-view-title", children: "Page view" }) }),
7429
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-seg rk-view-toggle", role: "group", "aria-label": "Page view (P to toggle)", children: [
7430
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7127
7431
  "button",
7128
7432
  {
7129
7433
  type: "button",
@@ -7133,7 +7437,7 @@ function Panel() {
7133
7437
  children: "Original"
7134
7438
  }
7135
7439
  ),
7136
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7440
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7137
7441
  "button",
7138
7442
  {
7139
7443
  type: "button",
@@ -7145,8 +7449,8 @@ function Panel() {
7145
7449
  )
7146
7450
  ] })
7147
7451
  ] }),
7148
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-panel-tools", children: [
7149
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
7452
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-panel-tools", children: [
7453
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
7150
7454
  "button",
7151
7455
  {
7152
7456
  type: "button",
@@ -7157,15 +7461,15 @@ function Panel() {
7157
7461
  dispatch({ type: "SET_MODE", mode: "pick" });
7158
7462
  },
7159
7463
  children: [
7160
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconTarget, { size: 14 }),
7464
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconTarget, { size: 14 }),
7161
7465
  " Pick an element"
7162
7466
  ]
7163
7467
  }
7164
7468
  ),
7165
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-search-row", children: [
7166
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-search", children: [
7167
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconSearch, { size: 13 }),
7168
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7469
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-search-row", children: [
7470
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-search", children: [
7471
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconSearch, { size: 13 }),
7472
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7169
7473
  "input",
7170
7474
  {
7171
7475
  ref: searchRef,
@@ -7177,8 +7481,8 @@ function Panel() {
7177
7481
  }
7178
7482
  )
7179
7483
  ] }),
7180
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-menu-scope rk-menu-anchor", children: [
7181
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
7484
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-menu-scope rk-menu-anchor", children: [
7485
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
7182
7486
  "button",
7183
7487
  {
7184
7488
  type: "button",
@@ -7190,15 +7494,15 @@ function Panel() {
7190
7494
  setSettingsOpen(false);
7191
7495
  },
7192
7496
  children: [
7193
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconFilter, { size: 13 }),
7497
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconFilter, { size: 13 }),
7194
7498
  "Filter",
7195
- typeFilter.size + prioFilter.size + statusFilter.size > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-badge-count", children: typeFilter.size + prioFilter.size + statusFilter.size })
7499
+ typeFilter.size + prioFilter.size + statusFilter.size > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-badge-count", children: typeFilter.size + prioFilter.size + statusFilter.size })
7196
7500
  ]
7197
7501
  }
7198
7502
  ),
7199
- filterOpen && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-menu", role: "menu", "aria-label": "Filter items", children: [
7200
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rk-menu-title", children: "Type" }),
7201
- TYPE_FILTERS.map((type) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
7503
+ filterOpen && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-menu", role: "menu", "aria-label": "Filter items", children: [
7504
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rk-menu-title", children: "Type" }),
7505
+ TYPE_FILTERS.map((type) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
7202
7506
  "button",
7203
7507
  {
7204
7508
  type: "button",
@@ -7212,16 +7516,16 @@ function Panel() {
7212
7516
  setTypeFilter(next);
7213
7517
  },
7214
7518
  children: [
7215
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: `rk-menu-check${typeFilter.has(type) ? " rk-on" : ""}` }),
7519
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: `rk-menu-check${typeFilter.has(type) ? " rk-on" : ""}` }),
7216
7520
  iconForType(type, 12),
7217
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-menu-label", children: type }),
7218
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-menu-count", children: typeCounts.get(type) ?? 0 })
7521
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-menu-label", children: type }),
7522
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-menu-count", children: typeCounts.get(type) ?? 0 })
7219
7523
  ]
7220
7524
  },
7221
7525
  type
7222
7526
  )),
7223
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rk-menu-title", children: "Priority" }),
7224
- PRIORITY_FILTERS.map((priority) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
7527
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rk-menu-title", children: "Priority" }),
7528
+ PRIORITY_FILTERS.map((priority) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
7225
7529
  "button",
7226
7530
  {
7227
7531
  type: "button",
@@ -7235,15 +7539,15 @@ function Panel() {
7235
7539
  setPrioFilter(next);
7236
7540
  },
7237
7541
  children: [
7238
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: `rk-menu-check${prioFilter.has(priority) ? " rk-on" : ""}` }),
7239
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-menu-label", children: PRIORITY_LABELS[priority] }),
7240
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-menu-count", children: prioCounts.get(priority) ?? 0 })
7542
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: `rk-menu-check${prioFilter.has(priority) ? " rk-on" : ""}` }),
7543
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-menu-label", children: PRIORITY_LABELS[priority] }),
7544
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-menu-count", children: prioCounts.get(priority) ?? 0 })
7241
7545
  ]
7242
7546
  },
7243
7547
  priority
7244
7548
  )),
7245
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rk-menu-title", children: "Status" }),
7246
- STATUSES.map((status) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
7549
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rk-menu-title", children: "Status" }),
7550
+ STATUSES.map((status) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
7247
7551
  "button",
7248
7552
  {
7249
7553
  type: "button",
@@ -7257,15 +7561,15 @@ function Panel() {
7257
7561
  setStatusFilter(next);
7258
7562
  },
7259
7563
  children: [
7260
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: `rk-menu-check${statusFilter.has(status) ? " rk-on" : ""}` }),
7261
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: `rk-status-dot rk-st-${status}` }),
7262
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-menu-label", children: status }),
7263
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-menu-count", children: statusCounts.get(status) ?? 0 })
7564
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: `rk-menu-check${statusFilter.has(status) ? " rk-on" : ""}` }),
7565
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: `rk-status-dot rk-st-${status}` }),
7566
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-menu-label", children: status }),
7567
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-menu-count", children: statusCounts.get(status) ?? 0 })
7264
7568
  ]
7265
7569
  },
7266
7570
  status
7267
7571
  )),
7268
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7572
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7269
7573
  "button",
7270
7574
  {
7271
7575
  type: "button",
@@ -7285,24 +7589,24 @@ function Panel() {
7285
7589
  ] })
7286
7590
  ] })
7287
7591
  ] }),
7288
- preview && unapplied > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-unplaced", role: "status", children: [
7592
+ preview && unapplied > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-unplaced", role: "status", children: [
7289
7593
  unapplied,
7290
7594
  " saved change",
7291
7595
  unapplied === 1 ? "" : "s",
7292
7596
  " couldn\u2019t be placed on the current page \u2014 the page has changed since they were captured."
7293
7597
  ] }),
7294
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rk-panel-list", children: filtered.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-empty", children: [
7295
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rk-empty-art", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconTarget, { size: 22 }) }),
7296
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rk-empty-title", children: items.length === 0 ? "Nothing here yet" : "No matches" }),
7297
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rk-empty-text", children: items.length === 0 ? "Choose \u201CPick an element\u201D, then click anything on the page. Your suggestions collect here, ready to hand off." : "Try a different search, or clear the filters." })
7298
- ] }) : groups.map(([route, entries]) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
7299
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rk-group-title", children: route }),
7598
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rk-panel-list", children: filtered.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-empty", children: [
7599
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rk-empty-art", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconTarget, { size: 22 }) }),
7600
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rk-empty-title", children: items.length === 0 ? "Nothing here yet" : "No matches" }),
7601
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rk-empty-text", children: items.length === 0 ? "Choose \u201CPick an element\u201D, then click anything on the page. Your suggestions collect here, ready to hand off." : "Try a different search, or clear the filters." })
7602
+ ] }) : groups.map(([route, entries]) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
7603
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rk-group-title", children: route }),
7300
7604
  entries.map(({ item, number }) => {
7301
7605
  const active2 = item.id === activeItemId;
7302
7606
  const orphan = orphanIds.has(item.id);
7303
7607
  const hiddenEl = hiddenIds.has(item.id);
7304
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
7305
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
7608
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
7609
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
7306
7610
  "div",
7307
7611
  {
7308
7612
  "data-item-id": item.id,
@@ -7317,18 +7621,18 @@ function Panel() {
7317
7621
  }
7318
7622
  },
7319
7623
  children: [
7320
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-item-num", children: number }),
7321
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-item-icon", children: iconForType(item.type) }),
7322
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "rk-item-body", children: [
7323
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "rk-item-snippet", children: [
7624
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-item-num", children: number }),
7625
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-item-icon", children: iconForType(item.type) }),
7626
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "rk-item-body", children: [
7627
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "rk-item-snippet", children: [
7324
7628
  item.target.tagName,
7325
7629
  " \xB7 ",
7326
7630
  item.target.textSnippet || item.target.selector
7327
7631
  ] }),
7328
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-item-change", children: changeSummary(item) }),
7329
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "rk-item-meta", children: [
7330
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "rk-author", title: new Date(item.createdAt).toLocaleString(), children: [
7331
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7632
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-item-change", children: changeSummary(item) }),
7633
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "rk-item-meta", children: [
7634
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "rk-author", title: new Date(item.createdAt).toLocaleString(), children: [
7635
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7332
7636
  "span",
7333
7637
  {
7334
7638
  className: "rk-author-dot",
@@ -7336,15 +7640,15 @@ function Panel() {
7336
7640
  }
7337
7641
  ),
7338
7642
  item.author?.name ?? "Unknown",
7339
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "rk-time", children: [
7643
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "rk-time", children: [
7340
7644
  " \xB7 ",
7341
7645
  timeAgo(item.createdAt)
7342
7646
  ] })
7343
7647
  ] }),
7344
- item.priority === "must" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-badge-mini rk-must", children: "must" }),
7345
- item.status !== "open" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: `rk-badge-mini rk-${item.status}`, children: item.status }),
7346
- orphan && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-badge-mini rk-orphaned", children: "unanchored" }),
7347
- hiddenEl && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7648
+ item.priority === "must" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-badge-mini rk-must", children: "must" }),
7649
+ item.status !== "open" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: `rk-badge-mini rk-${item.status}`, children: item.status }),
7650
+ orphan && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-badge-mini rk-orphaned", children: "unanchored" }),
7651
+ hiddenEl && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7348
7652
  "span",
7349
7653
  {
7350
7654
  className: "rk-badge-mini rk-hidden-el",
@@ -7352,14 +7656,14 @@ function Panel() {
7352
7656
  children: "hidden"
7353
7657
  }
7354
7658
  ),
7355
- item.note && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-badge-mini", children: "note" }),
7356
- peers.filter((peer) => peer.typingActive && peer.typingItemId === item.id).map((peer) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "rk-badge-mini", style: { color: peer.color }, children: [
7659
+ item.note && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-badge-mini", children: "note" }),
7660
+ peers.filter((peer) => peer.typingActive && peer.typingItemId === item.id).map((peer) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "rk-badge-mini", style: { color: peer.color }, children: [
7357
7661
  peer.name || "peer",
7358
7662
  " is writing\u2026"
7359
7663
  ] }, peer.id))
7360
7664
  ] })
7361
7665
  ] }),
7362
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-item-actions", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7666
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-item-actions", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7363
7667
  "button",
7364
7668
  {
7365
7669
  type: "button",
@@ -7374,15 +7678,15 @@ function Panel() {
7374
7678
  "info"
7375
7679
  );
7376
7680
  },
7377
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconTrash, { size: 13 })
7681
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconTrash, { size: 13 })
7378
7682
  }
7379
7683
  ) })
7380
7684
  ]
7381
7685
  }
7382
7686
  ),
7383
- active2 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-item-edit", children: [
7384
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-detail-byline", title: new Date(item.createdAt).toLocaleString(), children: [
7385
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7687
+ active2 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-item-edit", children: [
7688
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-detail-byline", title: new Date(item.createdAt).toLocaleString(), children: [
7689
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7386
7690
  "span",
7387
7691
  {
7388
7692
  className: "rk-author-dot",
@@ -7393,8 +7697,8 @@ function Panel() {
7393
7697
  " \xB7 ",
7394
7698
  timeAgo(item.createdAt)
7395
7699
  ] }),
7396
- isCopyItem(item) && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(CopyDiff, { before: item.payload.before, after: item.payload.after }),
7397
- isImageItem(item) && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7700
+ isCopyItem(item) && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CopyDiff, { before: item.payload.before, after: item.payload.after }),
7701
+ isImageItem(item) && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7398
7702
  ImageDetail,
7399
7703
  {
7400
7704
  beforeSrc: item.payload.beforeSrc,
@@ -7402,7 +7706,7 @@ function Panel() {
7402
7706
  afterDataUrl: item.payload.afterDataUrl
7403
7707
  }
7404
7708
  ),
7405
- isKnownItemType(item.type) && item.type !== "move" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7709
+ isKnownItemType(item.type) && item.type !== "move" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7406
7710
  "textarea",
7407
7711
  {
7408
7712
  className: "rk-textarea",
@@ -7412,7 +7716,7 @@ function Panel() {
7412
7716
  onChange: (event) => dispatch({ type: "UPDATE_ITEM", id: item.id, patch: withPayloadText(item, event.target.value) })
7413
7717
  }
7414
7718
  ),
7415
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7719
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7416
7720
  "input",
7417
7721
  {
7418
7722
  className: "rk-input",
@@ -7422,9 +7726,9 @@ function Panel() {
7422
7726
  onChange: (event) => dispatch({ type: "UPDATE_ITEM", id: item.id, patch: { note: event.target.value } })
7423
7727
  }
7424
7728
  ),
7425
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: [
7426
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-seg", role: "group", "aria-label": "Priority", children: [
7427
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7729
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: [
7730
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-seg", role: "group", "aria-label": "Priority", children: [
7731
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7428
7732
  "button",
7429
7733
  {
7430
7734
  type: "button",
@@ -7433,7 +7737,7 @@ function Panel() {
7433
7737
  children: "Must"
7434
7738
  }
7435
7739
  ),
7436
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7740
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7437
7741
  "button",
7438
7742
  {
7439
7743
  type: "button",
@@ -7443,7 +7747,7 @@ function Panel() {
7443
7747
  }
7444
7748
  )
7445
7749
  ] }),
7446
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "rk-seg", role: "group", "aria-label": "Status", children: STATUSES.map((status) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7750
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rk-seg", role: "group", "aria-label": "Status", children: STATUSES.map((status) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7447
7751
  "button",
7448
7752
  {
7449
7753
  type: "button",
@@ -7458,22 +7762,22 @@ function Panel() {
7458
7762
  ] }, item.id);
7459
7763
  })
7460
7764
  ] }, route)) }),
7461
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "rk-panel-foot", children: [
7462
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: onExport, disabled: items.length === 0, children: [
7463
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconDownload, { size: 13 }),
7765
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-panel-foot", children: [
7766
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: onExport, disabled: items.length === 0, children: [
7767
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconDownload, { size: 13 }),
7464
7768
  " Export"
7465
7769
  ] }),
7466
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => void onCopy(), disabled: items.length === 0, children: [
7467
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconCopyClipboard, { size: 13 }),
7770
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => void onCopy(), disabled: items.length === 0, children: [
7771
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconCopyClipboard, { size: 13 }),
7468
7772
  " Copy"
7469
7773
  ] }),
7470
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => importRef.current?.click(), children: [
7471
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconUpload, { size: 13 }),
7774
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { type: "button", className: "rk-btn rk-btn-ghost", onClick: () => importRef.current?.click(), children: [
7775
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconUpload, { size: 13 }),
7472
7776
  " Import"
7473
7777
  ] }),
7474
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "rk-spacer" }),
7475
- undoStack.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: "rk-icon-btn", "aria-label": "Undo last change (\u2318Z)", onClick: () => dispatch({ type: "UNDO" }), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(IconUndo, { size: 14 }) }),
7476
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7778
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "rk-spacer" }),
7779
+ undoStack.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: "rk-icon-btn", "aria-label": "Undo last change (\u2318Z)", onClick: () => dispatch({ type: "UNDO" }), children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconUndo, { size: 14 }) }),
7780
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7477
7781
  "button",
7478
7782
  {
7479
7783
  type: "button",
@@ -7484,7 +7788,7 @@ function Panel() {
7484
7788
  children: "Clear"
7485
7789
  }
7486
7790
  ),
7487
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7791
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7488
7792
  "button",
7489
7793
  {
7490
7794
  type: "button",
@@ -7494,7 +7798,7 @@ function Panel() {
7494
7798
  children: "Exit review"
7495
7799
  }
7496
7800
  ),
7497
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
7801
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7498
7802
  "input",
7499
7803
  {
7500
7804
  ref: importRef,
@@ -7515,8 +7819,8 @@ function Panel() {
7515
7819
  }
7516
7820
 
7517
7821
  // src/client/components/Presence.tsx
7518
- var import_react14 = require("react");
7519
- var import_jsx_runtime12 = require("react/jsx-runtime");
7822
+ var import_react15 = require("react");
7823
+ var import_jsx_runtime13 = require("react/jsx-runtime");
7520
7824
  function initials(name) {
7521
7825
  const parts = name.trim().split(/\s+/).filter(Boolean);
7522
7826
  if (parts.length === 0) return "\xB7";
@@ -7526,8 +7830,8 @@ function initials(name) {
7526
7830
  function PresenceStrip() {
7527
7831
  const { peers, syncStatus } = useStore();
7528
7832
  if (syncStatus === "disabled" || peers.length === 0) return null;
7529
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "rk-presence", "aria-label": `${peers.length} reviewer${peers.length === 1 ? "" : "s"} online`, children: [
7530
- peers.slice(0, 5).map((peer) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7833
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "rk-presence", "aria-label": `${peers.length} reviewer${peers.length === 1 ? "" : "s"} online`, children: [
7834
+ peers.slice(0, 5).map((peer) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7531
7835
  "span",
7532
7836
  {
7533
7837
  className: "rk-avatar",
@@ -7537,7 +7841,7 @@ function PresenceStrip() {
7537
7841
  },
7538
7842
  peer.id
7539
7843
  )),
7540
- peers.length > 5 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "rk-avatar rk-avatar-more", children: [
7844
+ peers.length > 5 && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "rk-avatar rk-avatar-more", children: [
7541
7845
  "+",
7542
7846
  peers.length - 5
7543
7847
  ] })
@@ -7545,13 +7849,13 @@ function PresenceStrip() {
7545
7849
  }
7546
7850
  function PeerCursors() {
7547
7851
  const { peers } = useStore();
7548
- const ringRefs = (0, import_react14.useRef)(/* @__PURE__ */ new Map());
7549
- const cursorRefs = (0, import_react14.useRef)(/* @__PURE__ */ new Map());
7550
- const motion = (0, import_react14.useRef)(
7852
+ const ringRefs = (0, import_react15.useRef)(/* @__PURE__ */ new Map());
7853
+ const cursorRefs = (0, import_react15.useRef)(/* @__PURE__ */ new Map());
7854
+ const motion = (0, import_react15.useRef)(
7551
7855
  /* @__PURE__ */ new Map()
7552
7856
  );
7553
- const peersRef = (0, import_react14.useRef)(peers);
7554
- const seen = (0, import_react14.useRef)(/* @__PURE__ */ new Map());
7857
+ const peersRef = (0, import_react15.useRef)(peers);
7858
+ const seen = (0, import_react15.useRef)(/* @__PURE__ */ new Map());
7555
7859
  for (const peer of peers) {
7556
7860
  const sig = peer.cursor ? `${peer.cursor.x},${peer.cursor.y},${peer.cursor.ox},${peer.cursor.oy}` : "";
7557
7861
  if (seen.current.get(peer.id) !== sig) {
@@ -7561,7 +7865,7 @@ function PeerCursors() {
7561
7865
  }
7562
7866
  }
7563
7867
  peersRef.current = peers;
7564
- (0, import_react14.useEffect)(() => {
7868
+ (0, import_react15.useEffect)(() => {
7565
7869
  const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
7566
7870
  let raf = 0;
7567
7871
  let last = performance.now();
@@ -7666,8 +7970,8 @@ function PeerCursors() {
7666
7970
  return () => cancelAnimationFrame(raf);
7667
7971
  }, []);
7668
7972
  if (peers.length === 0) return null;
7669
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
7670
- peers.map((peer) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
7973
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
7974
+ peers.map((peer) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
7671
7975
  "div",
7672
7976
  {
7673
7977
  ref: (el) => {
@@ -7679,7 +7983,7 @@ function PeerCursors() {
7679
7983
  },
7680
7984
  `ring-${peer.id}`
7681
7985
  )),
7682
- peers.map((peer, index) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
7986
+ peers.map((peer, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
7683
7987
  "div",
7684
7988
  {
7685
7989
  ref: (el) => {
@@ -7689,8 +7993,8 @@ function PeerCursors() {
7689
7993
  className: "rk-peer-cursor",
7690
7994
  style: { opacity: 0 },
7691
7995
  children: [
7692
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("svg", { width: "14", height: "18", viewBox: "0 0 14 18", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M1 1l12 7.5-5.2 1.2L5 16z", fill: peer.color, stroke: "rgba(0,0,0,0.25)", strokeWidth: "1" }) }),
7693
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
7996
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { width: "14", height: "18", viewBox: "0 0 14 18", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { d: "M1 1l12 7.5-5.2 1.2L5 16z", fill: peer.color, stroke: "rgba(0,0,0,0.25)", strokeWidth: "1" }) }),
7997
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
7694
7998
  "span",
7695
7999
  {
7696
8000
  className: "rk-peer-name",
@@ -7709,19 +8013,19 @@ function PeerCursors() {
7709
8013
  }
7710
8014
 
7711
8015
  // src/client/components/QuickComment.tsx
7712
- var import_react15 = require("react");
7713
- var import_jsx_runtime13 = require("react/jsx-runtime");
8016
+ var import_react16 = require("react");
8017
+ var import_jsx_runtime14 = require("react/jsx-runtime");
7714
8018
  function QuickComment() {
7715
8019
  const { quick } = useStore();
7716
8020
  if (!quick) return null;
7717
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(QuickComposerCard, { quick }, quick.id);
8021
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(QuickComposerCard, { quick }, quick.id);
7718
8022
  }
7719
8023
  function QuickComposerCard({ quick }) {
7720
8024
  const { route, session } = useStore();
7721
8025
  const dispatch = useDispatch();
7722
- const [text, setText] = (0, import_react15.useState)("");
7723
- const fieldRef = (0, import_react15.useRef)(null);
7724
- (0, import_react15.useEffect)(() => {
8026
+ const [text, setText] = (0, import_react16.useState)("");
8027
+ const fieldRef = (0, import_react16.useRef)(null);
8028
+ (0, import_react16.useEffect)(() => {
7725
8029
  const field = fieldRef.current;
7726
8030
  if (!field) return;
7727
8031
  field.focus();
@@ -7774,18 +8078,18 @@ function QuickComposerCard({ quick }) {
7774
8078
  };
7775
8079
  const x = Math.min(Math.max(quick.x, 8), window.innerWidth - 288);
7776
8080
  const y = Math.min(Math.max(quick.y + 12, 8), window.innerHeight - 108);
7777
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "rk-quick", style: { transform: `translate3d(${x}px, ${y}px, 0)` }, role: "dialog", "aria-label": "Quick comment", children: [
7778
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "rk-quick-head", children: [
7779
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconComment, { size: 12 }),
7780
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: "Comment here" }),
7781
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "rk-hint", children: [
7782
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "rk-kbd", children: "\u21B5" }),
8081
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "rk-quick", style: { transform: `translate3d(${x}px, ${y}px, 0)` }, role: "dialog", "aria-label": "Quick comment", children: [
8082
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "rk-quick-head", children: [
8083
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(IconComment, { size: 12 }),
8084
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: "Comment here" }),
8085
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "rk-hint", children: [
8086
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "rk-kbd", children: "\u21B5" }),
7783
8087
  " save \xB7 ",
7784
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "rk-kbd", children: "esc" }),
8088
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "rk-kbd", children: "esc" }),
7785
8089
  " cancel"
7786
8090
  ] })
7787
8091
  ] }),
7788
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
8092
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
7789
8093
  "textarea",
7790
8094
  {
7791
8095
  ref: fieldRef,
@@ -7801,8 +8105,8 @@ function QuickComposerCard({ quick }) {
7801
8105
  }
7802
8106
 
7803
8107
  // src/client/components/PickLayer.tsx
7804
- var import_react16 = require("react");
7805
- var import_jsx_runtime14 = require("react/jsx-runtime");
8108
+ var import_react17 = require("react");
8109
+ var import_jsx_runtime15 = require("react/jsx-runtime");
7806
8110
  function applyRect(el, rect, pad2 = 3) {
7807
8111
  if (!el) return;
7808
8112
  if (!rect) {
@@ -7817,14 +8121,14 @@ function applyRect(el, rect, pad2 = 3) {
7817
8121
  function PickLayer() {
7818
8122
  const { mode, selection, tool } = useStore();
7819
8123
  const dispatch = useDispatch();
7820
- const [hovered, setHovered] = (0, import_react16.useState)(null);
7821
- const outlineRef = (0, import_react16.useRef)(null);
7822
- const labelRef = (0, import_react16.useRef)(null);
7823
- const selectRef = (0, import_react16.useRef)(null);
7824
- const hoveredRef = (0, import_react16.useRef)(null);
8124
+ const [hovered, setHovered] = (0, import_react17.useState)(null);
8125
+ const outlineRef = (0, import_react17.useRef)(null);
8126
+ const labelRef = (0, import_react17.useRef)(null);
8127
+ const selectRef = (0, import_react17.useRef)(null);
8128
+ const hoveredRef = (0, import_react17.useRef)(null);
7825
8129
  hoveredRef.current = hovered;
7826
8130
  const picking = mode === "pick";
7827
- (0, import_react16.useEffect)(() => {
8131
+ (0, import_react17.useEffect)(() => {
7828
8132
  if (!picking) {
7829
8133
  setHovered(null);
7830
8134
  return;
@@ -7868,7 +8172,7 @@ function PickLayer() {
7868
8172
  document.removeEventListener("mousedown", swallow, true);
7869
8173
  };
7870
8174
  }, [picking, tool, dispatch]);
7871
- (0, import_react16.useEffect)(() => {
8175
+ (0, import_react17.useEffect)(() => {
7872
8176
  if (!picking) return;
7873
8177
  const body = document.body;
7874
8178
  const prev = body.style.cursor;
@@ -7878,7 +8182,7 @@ function PickLayer() {
7878
8182
  if (body.getAttribute("style") === "") body.removeAttribute("style");
7879
8183
  };
7880
8184
  }, [picking, tool]);
7881
- (0, import_react16.useEffect)(() => {
8185
+ (0, import_react17.useEffect)(() => {
7882
8186
  let raf = 0;
7883
8187
  const tick = () => {
7884
8188
  const hoverEl = hoveredRef.current;
@@ -7904,21 +8208,21 @@ function PickLayer() {
7904
8208
  raf = requestAnimationFrame(tick);
7905
8209
  return () => cancelAnimationFrame(raf);
7906
8210
  }, [picking, selection]);
7907
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
7908
- picking && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
7909
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { ref: outlineRef, className: "rk-outline", style: { opacity: 0 } }),
7910
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { ref: labelRef, className: "rk-outline-label", style: { opacity: 0 }, children: hovered ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
7911
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "rk-tag", children: hovered.tagName.toLowerCase() }),
7912
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: describeElement(hovered).replace(/^[a-z0-9]+ · /, "") })
7913
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { children: "Pick an element" }) })
8211
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
8212
+ picking && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
8213
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { ref: outlineRef, className: "rk-outline", style: { opacity: 0 } }),
8214
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { ref: labelRef, className: "rk-outline-label", style: { opacity: 0 }, children: hovered ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
8215
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "rk-tag", children: hovered.tagName.toLowerCase() }),
8216
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: describeElement(hovered).replace(/^[a-z0-9]+ · /, "") })
8217
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: "Pick an element" }) })
7914
8218
  ] }),
7915
- selection && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { ref: selectRef, className: "rk-select-ring", style: { opacity: 0 } })
8219
+ selection && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { ref: selectRef, className: "rk-select-ring", style: { opacity: 0 } })
7916
8220
  ] });
7917
8221
  }
7918
8222
 
7919
8223
  // src/client/components/Pins.tsx
7920
- var import_react17 = require("react");
7921
- var import_jsx_runtime15 = require("react/jsx-runtime");
8224
+ var import_react18 = require("react");
8225
+ var import_jsx_runtime16 = require("react/jsx-runtime");
7922
8226
  var STATUS_LABELS = {
7923
8227
  open: "open",
7924
8228
  accepted: "accepted",
@@ -7930,22 +8234,22 @@ var RERESOLVE_MS = 800;
7930
8234
  function Pins() {
7931
8235
  const { session, route, activeItemId, editor, mode, filter } = useStore();
7932
8236
  const dispatch = useDispatch();
7933
- const [clusters, setClusters] = (0, import_react17.useState)([]);
7934
- const [orphanCount, setOrphanCount] = (0, import_react17.useState)(0);
7935
- const wrapRefs = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
7936
- const visibleCache = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
7937
- const highlightRef = (0, import_react17.useRef)(null);
7938
- const hoveredItems = (0, import_react17.useRef)(null);
7939
- const elementCache = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
7940
- const lastResolve = (0, import_react17.useRef)(0);
7941
- const [newestId, setNewestId] = (0, import_react17.useState)(null);
7942
- const prevCount = (0, import_react17.useRef)(0);
7943
- const items = (0, import_react17.useMemo)(() => {
8237
+ const [clusters, setClusters] = (0, import_react18.useState)([]);
8238
+ const [orphanCount, setOrphanCount] = (0, import_react18.useState)(0);
8239
+ const wrapRefs = (0, import_react18.useRef)(/* @__PURE__ */ new Map());
8240
+ const visibleCache = (0, import_react18.useRef)(/* @__PURE__ */ new Map());
8241
+ const highlightRef = (0, import_react18.useRef)(null);
8242
+ const hoveredItems = (0, import_react18.useRef)(null);
8243
+ const elementCache = (0, import_react18.useRef)(/* @__PURE__ */ new Map());
8244
+ const lastResolve = (0, import_react18.useRef)(0);
8245
+ const [newestId, setNewestId] = (0, import_react18.useState)(null);
8246
+ const prevCount = (0, import_react18.useRef)(0);
8247
+ const items = (0, import_react18.useMemo)(() => {
7944
8248
  const all = session?.items ?? [];
7945
8249
  return all.map((item, index) => ({ item, number: index + 1 })).filter(({ item }) => item.route === route);
7946
8250
  }, [session, route]);
7947
8251
  const byId = new Map(items.map(({ item }) => [item.id, item]));
7948
- (0, import_react17.useEffect)(() => {
8252
+ (0, import_react18.useEffect)(() => {
7949
8253
  const count = session?.items.length ?? 0;
7950
8254
  if (count > prevCount.current && session && count > 0) {
7951
8255
  const id = session.items[count - 1].id;
@@ -7956,7 +8260,7 @@ function Pins() {
7956
8260
  }
7957
8261
  prevCount.current = count;
7958
8262
  }, [session]);
7959
- (0, import_react17.useEffect)(() => {
8263
+ (0, import_react18.useEffect)(() => {
7960
8264
  let raf = 0;
7961
8265
  let lastSignature = "";
7962
8266
  const tick = (time) => {
@@ -8040,8 +8344,8 @@ function Pins() {
8040
8344
  dispatch({ type: "SET_ACTIVE_ITEM", id });
8041
8345
  dispatch({ type: "SET_PANEL", open: true });
8042
8346
  };
8043
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
8044
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { ref: highlightRef, className: "rk-hl-ring", style: { opacity: 0 } }),
8347
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
8348
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref: highlightRef, className: "rk-hl-ring", style: { opacity: 0 } }),
8045
8349
  clusters.map((cluster) => {
8046
8350
  const isCluster = cluster.ids.length > 1;
8047
8351
  const isActive = activeItemId !== null && cluster.ids.includes(activeItemId);
@@ -8051,7 +8355,7 @@ function Pins() {
8051
8355
  const status = statuses.size === 1 ? members[0]?.status : void 0;
8052
8356
  const dimmed = filter !== null && members.length > 0 && !members.some((item) => itemMatchesFilter(item, filter));
8053
8357
  const statusHint = isCluster ? status ? ` \xB7 all ${STATUS_LABELS[status] ?? status}` : "" : ` \xB7 ${STATUS_LABELS[members[0]?.status ?? "open"] ?? members[0]?.status}`;
8054
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8358
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
8055
8359
  "div",
8056
8360
  {
8057
8361
  ref: (el) => {
@@ -8059,7 +8363,7 @@ function Pins() {
8059
8363
  else wrapRefs.current.delete(cluster.key);
8060
8364
  },
8061
8365
  className: `rk-pin-wrap${picking ? " rk-pin-ghost" : ""}`,
8062
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8366
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
8063
8367
  "button",
8064
8368
  {
8065
8369
  type: "button",
@@ -8081,7 +8385,7 @@ function Pins() {
8081
8385
  cluster.key
8082
8386
  );
8083
8387
  }),
8084
- orphanCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8388
+ orphanCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
8085
8389
  "button",
8086
8390
  {
8087
8391
  type: "button",
@@ -8089,7 +8393,7 @@ function Pins() {
8089
8393
  style: { bottom: 16, left: "50%", transform: "translateX(-50%)" },
8090
8394
  onClick: () => dispatch({ type: "SET_PANEL", open: true }),
8091
8395
  children: [
8092
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(IconUnlink, { size: 13 }),
8396
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IconUnlink, { size: 13 }),
8093
8397
  orphanCount,
8094
8398
  " unanchored item",
8095
8399
  orphanCount === 1 ? "" : "s"
@@ -8100,13 +8404,13 @@ function Pins() {
8100
8404
  }
8101
8405
 
8102
8406
  // src/client/components/Toolbar.tsx
8103
- var import_react18 = require("react");
8104
- var import_jsx_runtime16 = require("react/jsx-runtime");
8407
+ var import_react19 = require("react");
8408
+ var import_jsx_runtime17 = require("react/jsx-runtime");
8105
8409
  var TOOLS = [
8106
- { tool: "select", hintId: "tool-select", label: "Select", icon: (n) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IconTarget, { size: n }) },
8107
- { tool: "copy", hintId: "tool-copy", label: "Rewrite text", icon: (n) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IconText, { size: n }) },
8108
- { tool: "comment", hintId: "tool-comment", label: "Comment", icon: (n) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IconComment, { size: n }) },
8109
- { tool: "image", hintId: "tool-image", label: "Replace image", icon: (n) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IconImage, { size: n }) }
8410
+ { tool: "select", hintId: "tool-select", label: "Select", icon: (n) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconTarget, { size: n }) },
8411
+ { tool: "copy", hintId: "tool-copy", label: "Rewrite text", icon: (n) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconText, { size: n }) },
8412
+ { tool: "comment", hintId: "tool-comment", label: "Comment", icon: (n) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconComment, { size: n }) },
8413
+ { tool: "image", hintId: "tool-image", label: "Replace image", icon: (n) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconImage, { size: n }) }
8110
8414
  ];
8111
8415
  var POS_KEY2 = "review-kit:toolbar-pos";
8112
8416
  var DRAG_THRESHOLD2 = 6;
@@ -8114,24 +8418,24 @@ function Toolbar() {
8114
8418
  const { selection, editor, peers, syncStatus, mode, tool } = useStore();
8115
8419
  const hasPeers = syncStatus !== "disabled" && peers.length > 0;
8116
8420
  const dispatch = useDispatch();
8117
- const ref = (0, import_react18.useRef)(null);
8118
- const [pos, setPos] = (0, import_react18.useState)(() => readStoredPos(POS_KEY2));
8119
- const drag = (0, import_react18.useRef)(null);
8120
- const [dragging, setDragging] = (0, import_react18.useState)(false);
8121
- const justDragged = (0, import_react18.useRef)(false);
8421
+ const ref = (0, import_react19.useRef)(null);
8422
+ const [pos, setPos] = (0, import_react19.useState)(() => readStoredPos(POS_KEY2));
8423
+ const drag = (0, import_react19.useRef)(null);
8424
+ const [dragging, setDragging] = (0, import_react19.useState)(false);
8425
+ const justDragged = (0, import_react19.useRef)(false);
8122
8426
  const visible = editor === null || selection !== null;
8123
8427
  const applyPos = (next, persist) => {
8124
8428
  if (next === null && ref.current) ref.current.style.bottom = "";
8125
8429
  setPos(next);
8126
8430
  if (persist) storePos(POS_KEY2, next);
8127
8431
  };
8128
- (0, import_react18.useLayoutEffect)(() => {
8432
+ (0, import_react19.useLayoutEffect)(() => {
8129
8433
  const el = ref.current;
8130
8434
  if (!el || pos === null) return;
8131
8435
  const clamped = clampPos(pos.x, pos.y, el.offsetWidth, el.offsetHeight);
8132
8436
  if (clamped.x !== pos.x || clamped.y !== pos.y) applyPos(clamped, true);
8133
8437
  }, [pos, selection, hasPeers, visible]);
8134
- (0, import_react18.useEffect)(() => {
8438
+ (0, import_react19.useEffect)(() => {
8135
8439
  const onResize = () => {
8136
8440
  const el = ref.current;
8137
8441
  const current = readStoredPos(POS_KEY2);
@@ -8244,7 +8548,7 @@ function Toolbar() {
8244
8548
  mode === "pick" && selection === null ? "rk-ghost" : ""
8245
8549
  ].filter(Boolean).join(" ");
8246
8550
  const style = pos !== null ? { left: 0, top: 0, translate: `${pos.x}px ${pos.y}px` } : void 0;
8247
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
8551
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
8248
8552
  "div",
8249
8553
  {
8250
8554
  ref,
@@ -8255,7 +8559,7 @@ function Toolbar() {
8255
8559
  onPointerDown,
8256
8560
  onClickCapture,
8257
8561
  children: [
8258
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
8562
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
8259
8563
  "button",
8260
8564
  {
8261
8565
  type: "button",
@@ -8263,19 +8567,19 @@ function Toolbar() {
8263
8567
  "aria-label": "Move the toolbar \u2014 drag, or use arrow keys; double-click to reset its position",
8264
8568
  onKeyDown: onGripKeyDown,
8265
8569
  onDoubleClick: () => applyPos(null, true),
8266
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("svg", { width: "8", height: "14", viewBox: "0 0 8 14", "aria-hidden": "true", fill: "currentColor", children: [
8267
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("circle", { cx: "2", cy: "2", r: "1.1" }),
8268
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("circle", { cx: "6", cy: "2", r: "1.1" }),
8269
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("circle", { cx: "2", cy: "7", r: "1.1" }),
8270
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("circle", { cx: "6", cy: "7", r: "1.1" }),
8271
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("circle", { cx: "2", cy: "12", r: "1.1" }),
8272
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("circle", { cx: "6", cy: "12", r: "1.1" })
8570
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("svg", { width: "8", height: "14", viewBox: "0 0 8 14", "aria-hidden": "true", fill: "currentColor", children: [
8571
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("circle", { cx: "2", cy: "2", r: "1.1" }),
8572
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("circle", { cx: "6", cy: "2", r: "1.1" }),
8573
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("circle", { cx: "2", cy: "7", r: "1.1" }),
8574
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("circle", { cx: "6", cy: "7", r: "1.1" }),
8575
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("circle", { cx: "2", cy: "12", r: "1.1" }),
8576
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("circle", { cx: "6", cy: "12", r: "1.1" })
8273
8577
  ] })
8274
8578
  }
8275
8579
  ),
8276
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(PresenceStrip, {}),
8277
- hasPeers && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "rk-toolbar-sep" }),
8278
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "rk-seg rk-tool-seg", role: "group", "aria-label": "Tools", children: TOOLS.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
8580
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(PresenceStrip, {}),
8581
+ hasPeers && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "rk-toolbar-sep" }),
8582
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "rk-seg rk-tool-seg", role: "group", "aria-label": "Tools", children: TOOLS.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
8279
8583
  "button",
8280
8584
  {
8281
8585
  type: "button",
@@ -8287,9 +8591,9 @@ function Toolbar() {
8287
8591
  },
8288
8592
  entry.tool
8289
8593
  )) }),
8290
- selection && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "rk-toolbar-sep" }),
8291
- selection && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
8292
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
8594
+ selection && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "rk-toolbar-sep" }),
8595
+ selection && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
8596
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
8293
8597
  "button",
8294
8598
  {
8295
8599
  type: "button",
@@ -8298,12 +8602,12 @@ function Toolbar() {
8298
8602
  title: `Rewrite text${keyHint("tool-copy")}`,
8299
8603
  onClick: () => open("copy"),
8300
8604
  children: [
8301
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IconText, { size: 14 }),
8605
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconText, { size: 14 }),
8302
8606
  " Rewrite"
8303
8607
  ]
8304
8608
  }
8305
8609
  ),
8306
- hasImage && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
8610
+ hasImage && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
8307
8611
  "button",
8308
8612
  {
8309
8613
  type: "button",
@@ -8312,12 +8616,12 @@ function Toolbar() {
8312
8616
  title: `Replace image${keyHint("tool-image")}`,
8313
8617
  onClick: () => open("image"),
8314
8618
  children: [
8315
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IconImage, { size: 14 }),
8619
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconImage, { size: 14 }),
8316
8620
  " Image"
8317
8621
  ]
8318
8622
  }
8319
8623
  ),
8320
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
8624
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
8321
8625
  "button",
8322
8626
  {
8323
8627
  type: "button",
@@ -8326,32 +8630,32 @@ function Toolbar() {
8326
8630
  title: `Comment${keyHint("tool-comment")}`,
8327
8631
  onClick: () => open("comment"),
8328
8632
  children: [
8329
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IconComment, { size: 14 }),
8633
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconComment, { size: 14 }),
8330
8634
  " Comment"
8331
8635
  ]
8332
8636
  }
8333
8637
  ),
8334
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { type: "button", role: "menuitem", className: "rk-toolbar-btn", onClick: () => open("style"), children: [
8335
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IconStyle, { size: 14 }),
8638
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("button", { type: "button", role: "menuitem", className: "rk-toolbar-btn", onClick: () => open("style"), children: [
8639
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconStyle, { size: 14 }),
8336
8640
  " Style"
8337
8641
  ] }),
8338
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { type: "button", role: "menuitem", className: "rk-toolbar-btn", onClick: () => open("move"), children: [
8339
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IconMove, { size: 14 }),
8642
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("button", { type: "button", role: "menuitem", className: "rk-toolbar-btn", onClick: () => open("move"), children: [
8643
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconMove, { size: 14 }),
8340
8644
  " Move"
8341
8645
  ] }),
8342
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { type: "button", role: "menuitem", className: "rk-toolbar-btn rk-danger", onClick: () => open("delete"), children: [
8343
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IconTrash, { size: 14 }),
8646
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("button", { type: "button", role: "menuitem", className: "rk-toolbar-btn rk-danger", onClick: () => open("delete"), children: [
8647
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconTrash, { size: 14 }),
8344
8648
  " Delete"
8345
8649
  ] }),
8346
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "rk-toolbar-sep" }),
8347
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
8650
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "rk-toolbar-sep" }),
8651
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
8348
8652
  "button",
8349
8653
  {
8350
8654
  type: "button",
8351
8655
  className: "rk-icon-btn",
8352
8656
  "aria-label": "Deselect",
8353
8657
  onClick: () => dispatch({ type: "SELECT", selection: null }),
8354
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(IconClose, { size: 13 })
8658
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconClose, { size: 13 })
8355
8659
  }
8356
8660
  )
8357
8661
  ] })
@@ -8361,40 +8665,40 @@ function Toolbar() {
8361
8665
  }
8362
8666
 
8363
8667
  // src/client/components/Toast.tsx
8364
- var import_react19 = require("react");
8365
- var import_jsx_runtime17 = require("react/jsx-runtime");
8668
+ var import_react20 = require("react");
8669
+ var import_jsx_runtime18 = require("react/jsx-runtime");
8366
8670
  function ToastRow({ toast: toast2 }) {
8367
8671
  const dispatch = useDispatch();
8368
- (0, import_react19.useEffect)(() => {
8672
+ (0, import_react20.useEffect)(() => {
8369
8673
  const timer = window.setTimeout(() => dispatch({ type: "DISMISS_TOAST", id: toast2.id }), 3500);
8370
8674
  return () => window.clearTimeout(timer);
8371
8675
  }, [toast2.id, dispatch]);
8372
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: `rk-toast rk-${toast2.tone}`, role: "status", children: [
8373
- toast2.tone === "success" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconCheck, { size: 13 }) : toast2.tone === "error" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconWarning, { size: 13 }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(IconComment, { size: 13 }),
8676
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: `rk-toast rk-${toast2.tone}`, role: "status", children: [
8677
+ toast2.tone === "success" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconCheck, { size: 13 }) : toast2.tone === "error" ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconWarning, { size: 13 }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(IconComment, { size: 13 }),
8374
8678
  toast2.message
8375
8679
  ] });
8376
8680
  }
8377
8681
  function ToastHost() {
8378
8682
  const { toasts } = useStore();
8379
8683
  if (toasts.length === 0) return null;
8380
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "rk-toasts", "aria-live": "polite", children: toasts.map((toast2) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ToastRow, { toast: toast2 }, toast2.id)) });
8684
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "rk-toasts", "aria-live": "polite", children: toasts.map((toast2) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ToastRow, { toast: toast2 }, toast2.id)) });
8381
8685
  }
8382
8686
 
8383
8687
  // src/client/App.tsx
8384
- var import_jsx_runtime18 = require("react/jsx-runtime");
8688
+ var import_jsx_runtime19 = require("react/jsx-runtime");
8385
8689
  function App() {
8386
8690
  const state = useStore();
8387
8691
  const rawDispatch = useDispatch();
8388
8692
  const config = useConfig();
8389
8693
  const dispatch = useRealtimeSync(state, rawDispatch, config, reducer);
8390
- const saveTimer = (0, import_react20.useRef)(null);
8694
+ const saveTimer = (0, import_react21.useRef)(null);
8391
8695
  const storageKey = config.storageKey ?? defaultStorageKey();
8392
8696
  useAppliedPreview(state, dispatch);
8393
8697
  useEmblemaAutoPush(state, dispatch, config);
8394
- (0, import_react20.useEffect)(() => {
8698
+ (0, import_react21.useEffect)(() => {
8395
8699
  setEffectAssetsUrl(config.fxAssetsUrl);
8396
8700
  }, [config.fxAssetsUrl]);
8397
- (0, import_react20.useEffect)(() => {
8701
+ (0, import_react21.useEffect)(() => {
8398
8702
  let cancelled = false;
8399
8703
  void loadSession(storageKey).then((stored) => {
8400
8704
  if (cancelled) return;
@@ -8412,7 +8716,7 @@ function App() {
8412
8716
  };
8413
8717
  }, [storageKey, config.defaultAuthor, dispatch]);
8414
8718
  const { session } = state;
8415
- (0, import_react20.useEffect)(() => {
8719
+ (0, import_react21.useEffect)(() => {
8416
8720
  if (!session) return;
8417
8721
  if (saveTimer.current !== null) window.clearTimeout(saveTimer.current);
8418
8722
  saveTimer.current = window.setTimeout(() => {
@@ -8423,9 +8727,9 @@ function App() {
8423
8727
  if (saveTimer.current !== null) window.clearTimeout(saveTimer.current);
8424
8728
  };
8425
8729
  }, [session, storageKey, config]);
8426
- (0, import_react20.useEffect)(() => trackPointer(), []);
8427
- const focusHandled = (0, import_react20.useRef)(false);
8428
- (0, import_react20.useEffect)(() => {
8730
+ (0, import_react21.useEffect)(() => trackPointer(), []);
8731
+ const focusHandled = (0, import_react21.useRef)(false);
8732
+ (0, import_react21.useEffect)(() => {
8429
8733
  if (!session || focusHandled.current) return;
8430
8734
  focusHandled.current = true;
8431
8735
  const id = takeFocusItem();
@@ -8450,7 +8754,7 @@ function App() {
8450
8754
  cancelled = true;
8451
8755
  };
8452
8756
  }, [session, dispatch]);
8453
- (0, import_react20.useEffect)(() => {
8757
+ (0, import_react21.useEffect)(() => {
8454
8758
  let last = window.location.pathname;
8455
8759
  const check = () => {
8456
8760
  if (window.location.pathname !== last) {
@@ -8465,9 +8769,9 @@ function App() {
8465
8769
  window.removeEventListener("popstate", check);
8466
8770
  };
8467
8771
  }, [dispatch]);
8468
- const stateRef = (0, import_react20.useRef)(state);
8772
+ const stateRef = (0, import_react21.useRef)(state);
8469
8773
  stateRef.current = state;
8470
- (0, import_react20.useEffect)(() => {
8774
+ (0, import_react21.useEffect)(() => {
8471
8775
  const onKeyDown = (event) => {
8472
8776
  const current = stateRef.current;
8473
8777
  for (const def of SHORTCUTS) {
@@ -8482,7 +8786,7 @@ function App() {
8482
8786
  document.addEventListener("keydown", onKeyDown);
8483
8787
  return () => document.removeEventListener("keydown", onKeyDown);
8484
8788
  }, [dispatch]);
8485
- (0, import_react20.useEffect)(() => {
8789
+ (0, import_react21.useEffect)(() => {
8486
8790
  if (!state.editor) return;
8487
8791
  const editorEl = state.editor.element;
8488
8792
  const onPointerDown = (event) => {
@@ -8495,32 +8799,33 @@ function App() {
8495
8799
  }, [state.editor, dispatch]);
8496
8800
  if (!session) return null;
8497
8801
  if (state.hidden) {
8498
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(DispatchContext.Provider, { value: dispatch, children: null });
8802
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(DispatchContext.Provider, { value: dispatch, children: null });
8499
8803
  }
8500
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(DispatchContext.Provider, { value: dispatch, children: [
8501
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PickLayer, {}),
8502
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PeerCursors, {}),
8503
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Toolbar, {}),
8504
- state.editor?.kind === "copy" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(InlineEditor, { editor: state.editor }),
8505
- state.editor?.kind === "image" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ImageEditor, { editor: state.editor }),
8506
- state.editor && state.editor.kind !== "copy" && state.editor.kind !== "image" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(NoteComposer, { editor: state.editor }),
8507
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Pins, {}),
8508
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(QuickComment, {}),
8509
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Panel, {}),
8510
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Badge, {}),
8511
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(CoachMark, {}),
8512
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(CheatSheet, {}),
8513
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(EmblemaConnect, {}),
8514
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ConfirmHost, {}),
8515
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ToastHost, {})
8804
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(DispatchContext.Provider, { value: dispatch, children: [
8805
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PickLayer, {}),
8806
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PeerCursors, {}),
8807
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Toolbar, {}),
8808
+ state.editor?.kind === "copy" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(InlineEditor, { editor: state.editor }),
8809
+ state.editor?.kind === "image" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ImageEditor, { editor: state.editor }),
8810
+ state.editor && state.editor.kind !== "copy" && state.editor.kind !== "image" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(NoteComposer, { editor: state.editor }),
8811
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Pins, {}),
8812
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(QuickComment, {}),
8813
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Panel, {}),
8814
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Badge, {}),
8815
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CoachMark, {}),
8816
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CheatSheet, {}),
8817
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(EmblemaConnect, {}),
8818
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(TerminalConnect, {}),
8819
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ConfirmHost, {}),
8820
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ToastHost, {})
8516
8821
  ] });
8517
8822
  }
8518
8823
 
8519
8824
  // src/client/ReviewKit.tsx
8520
- var import_jsx_runtime19 = require("react/jsx-runtime");
8825
+ var import_jsx_runtime20 = require("react/jsx-runtime");
8521
8826
  function ShadowHost({ config }) {
8522
- const [mount, setMount] = (0, import_react21.useState)(null);
8523
- (0, import_react21.useEffect)(() => {
8827
+ const [mount, setMount] = (0, import_react22.useState)(null);
8828
+ (0, import_react22.useEffect)(() => {
8524
8829
  const host = document.createElement("div");
8525
8830
  host.setAttribute(HOST_ATTR, "");
8526
8831
  host.style.position = "fixed";
@@ -8556,15 +8861,15 @@ function ShadowHost({ config }) {
8556
8861
  }, []);
8557
8862
  if (!mount) return null;
8558
8863
  return (0, import_react_dom.createPortal)(
8559
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(StoreProvider, { config, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(App, {}) }),
8864
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(StoreProvider, { config, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(App, {}) }),
8560
8865
  mount
8561
8866
  );
8562
8867
  }
8563
8868
  function ReviewKit(props) {
8564
- const [active2, setActive] = (0, import_react21.useState)(false);
8869
+ const [active2, setActive] = (0, import_react22.useState)(false);
8565
8870
  const { enabled, token, allowedHosts, devAutoOn } = props;
8566
8871
  const hostsKey = allowedHosts?.join("\0") ?? "";
8567
- (0, import_react21.useEffect)(() => {
8872
+ (0, import_react22.useEffect)(() => {
8568
8873
  if (enabled === false) {
8569
8874
  setActive(false);
8570
8875
  return;
@@ -8575,7 +8880,7 @@ function ReviewKit(props) {
8575
8880
  return () => window.removeEventListener(DEACTIVATE_EVENT, onDeactivate);
8576
8881
  }, [enabled, token, devAutoOn, hostsKey]);
8577
8882
  if (enabled === false || !active2) return null;
8578
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ShadowHost, { config: props });
8883
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ShadowHost, { config: props });
8579
8884
  }
8580
8885
  // Annotate the CommonJS export names for ESM import in node:
8581
8886
  0 && (module.exports = {