jazz-tools 0.19.2 → 0.19.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/.svelte-kit/__package__/jazz.class.svelte.d.ts +2 -2
  2. package/.svelte-kit/__package__/jazz.class.svelte.d.ts.map +1 -1
  3. package/.svelte-kit/__package__/jazz.class.svelte.js +15 -17
  4. package/.turbo/turbo-build.log +54 -54
  5. package/CHANGELOG.md +24 -0
  6. package/dist/{chunk-NCNM6UDZ.js → chunk-PT7FCV26.js} +148 -78
  7. package/dist/chunk-PT7FCV26.js.map +1 -0
  8. package/dist/index.js +14 -7
  9. package/dist/index.js.map +1 -1
  10. package/dist/inspector/{custom-element-ABVPHX53.js → custom-element-P76EIWEV.js} +322 -146
  11. package/dist/inspector/custom-element-P76EIWEV.js.map +1 -0
  12. package/dist/inspector/index.js +302 -126
  13. package/dist/inspector/index.js.map +1 -1
  14. package/dist/inspector/register-custom-element.js +1 -1
  15. package/dist/inspector/tests/viewer/co-plain-text-view.test.d.ts +2 -0
  16. package/dist/inspector/tests/viewer/co-plain-text-view.test.d.ts.map +1 -0
  17. package/dist/inspector/utils/history.d.ts +5 -1
  18. package/dist/inspector/utils/history.d.ts.map +1 -1
  19. package/dist/inspector/utils/permissions.d.ts +3 -0
  20. package/dist/inspector/utils/permissions.d.ts.map +1 -0
  21. package/dist/inspector/viewer/co-map-view.d.ts.map +1 -1
  22. package/dist/inspector/viewer/co-plain-text-view.d.ts +4 -2
  23. package/dist/inspector/viewer/co-plain-text-view.d.ts.map +1 -1
  24. package/dist/inspector/viewer/grid-view.d.ts.map +1 -1
  25. package/dist/inspector/viewer/page.d.ts.map +1 -1
  26. package/dist/inspector/viewer/use-resolve-covalue.d.ts +0 -1
  27. package/dist/inspector/viewer/use-resolve-covalue.d.ts.map +1 -1
  28. package/dist/react-core/hooks.d.ts.map +1 -1
  29. package/dist/react-core/index.js +4 -17
  30. package/dist/react-core/index.js.map +1 -1
  31. package/dist/svelte/jazz.class.svelte.d.ts +2 -2
  32. package/dist/svelte/jazz.class.svelte.d.ts.map +1 -1
  33. package/dist/svelte/jazz.class.svelte.js +15 -17
  34. package/dist/testing.js +1 -1
  35. package/dist/tools/coValues/coFeed.d.ts.map +1 -1
  36. package/dist/tools/coValues/group.d.ts.map +1 -1
  37. package/dist/tools/coValues/interfaces.d.ts +7 -6
  38. package/dist/tools/coValues/interfaces.d.ts.map +1 -1
  39. package/dist/tools/coValues/promise.d.ts +9 -0
  40. package/dist/tools/coValues/promise.d.ts.map +1 -0
  41. package/dist/tools/coValues/request.d.ts.map +1 -1
  42. package/dist/tools/exports.d.ts +1 -1
  43. package/dist/tools/exports.d.ts.map +1 -1
  44. package/dist/tools/implementation/refs.d.ts +1 -1
  45. package/dist/tools/implementation/refs.d.ts.map +1 -1
  46. package/dist/tools/implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.d.ts +3 -1
  47. package/dist/tools/implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.d.ts.map +1 -1
  48. package/dist/tools/implementation/zodSchema/unionUtils.d.ts.map +1 -1
  49. package/dist/tools/subscribe/SubscriptionScope.d.ts +5 -2
  50. package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
  51. package/dist/tools/subscribe/index.d.ts +1 -1
  52. package/dist/tools/subscribe/index.d.ts.map +1 -1
  53. package/dist/tools/subscribe/types.d.ts +2 -1
  54. package/dist/tools/subscribe/types.d.ts.map +1 -1
  55. package/dist/tools/tests/SubscriptionScope.test.d.ts +2 -0
  56. package/dist/tools/tests/SubscriptionScope.test.d.ts.map +1 -0
  57. package/package.json +4 -4
  58. package/src/inspector/tests/utils/history.test.ts +233 -2
  59. package/src/inspector/tests/viewer/co-plain-text-view.test.tsx +125 -0
  60. package/src/inspector/tests/viewer/comap-view.test.tsx +309 -1
  61. package/src/inspector/tests/viewer/history-view.test.tsx +134 -2
  62. package/src/inspector/utils/history.ts +168 -1
  63. package/src/inspector/utils/permissions.ts +10 -0
  64. package/src/inspector/viewer/co-map-view.tsx +27 -15
  65. package/src/inspector/viewer/co-plain-text-view.tsx +102 -3
  66. package/src/inspector/viewer/grid-view.tsx +2 -1
  67. package/src/inspector/viewer/history-view.tsx +5 -23
  68. package/src/inspector/viewer/page.tsx +8 -1
  69. package/src/inspector/viewer/use-resolve-covalue.ts +2 -6
  70. package/src/react-core/hooks.ts +5 -29
  71. package/src/svelte/jazz.class.svelte.ts +16 -34
  72. package/src/tools/coValues/coFeed.ts +10 -7
  73. package/src/tools/coValues/coMap.ts +10 -7
  74. package/src/tools/coValues/group.ts +6 -2
  75. package/src/tools/coValues/interfaces.ts +48 -28
  76. package/src/tools/coValues/promise.ts +34 -0
  77. package/src/tools/coValues/request.ts +12 -8
  78. package/src/tools/exports.ts +1 -0
  79. package/src/tools/implementation/refs.ts +9 -17
  80. package/src/tools/implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.ts +62 -30
  81. package/src/tools/implementation/zodSchema/unionUtils.ts +3 -4
  82. package/src/tools/subscribe/SubscriptionScope.ts +45 -2
  83. package/src/tools/subscribe/index.ts +28 -13
  84. package/src/tools/subscribe/types.ts +5 -2
  85. package/src/tools/tests/SubscriptionScope.test.ts +397 -0
  86. package/src/tools/tests/deepLoading.test.ts +22 -0
  87. package/src/tools/tests/subscribe.test.ts +69 -0
  88. package/dist/chunk-NCNM6UDZ.js.map +0 -1
  89. package/dist/inspector/custom-element-ABVPHX53.js.map +0 -1
@@ -4,8 +4,8 @@
4
4
  import React8 from "react";
5
5
 
6
6
  // src/inspector/viewer/new-app.tsx
7
- import { styled as styled26 } from "goober";
8
- import { useState as useState16 } from "react";
7
+ import { styled as styled27 } from "goober";
8
+ import { useState as useState17 } from "react";
9
9
 
10
10
  // src/inspector/ui/button.tsx
11
11
  import { styled } from "goober";
@@ -180,10 +180,10 @@ var Breadcrumbs = ({
180
180
  };
181
181
 
182
182
  // src/inspector/viewer/page-stack.tsx
183
- import { styled as styled23 } from "goober";
183
+ import { styled as styled24 } from "goober";
184
184
 
185
185
  // src/inspector/viewer/page.tsx
186
- import { styled as styled21 } from "goober";
186
+ import { styled as styled22 } from "goober";
187
187
  import React5 from "react";
188
188
 
189
189
  // src/inspector/ui/badge.tsx
@@ -532,12 +532,6 @@ function RenderBlobImage({ blob }) {
532
532
  var isBrowserImage = (coValue) => {
533
533
  return "originalSize" in coValue && "placeholderDataURL" in coValue;
534
534
  };
535
- var isGroup = (coValue) => {
536
- return "readKey" in coValue;
537
- };
538
- var isAccount = (coValue) => {
539
- return isGroup(coValue) && "profile" in coValue;
540
- };
541
535
  async function resolveCoValue(coValueId, node) {
542
536
  const value = await node.load(coValueId);
543
537
  if (value === "unavailable") {
@@ -554,7 +548,7 @@ async function resolveCoValue(coValueId, node) {
554
548
  if (type === "comap") {
555
549
  if (isBrowserImage(snapshot)) {
556
550
  extendedType = "image";
557
- } else if (isAccount(snapshot)) {
551
+ } else if (value.headerMeta?.type === "account") {
558
552
  extendedType = "account";
559
553
  } else if (value.core.isGroup()) {
560
554
  extendedType = "group";
@@ -583,7 +577,7 @@ function subscribeToCoValue(coValueId, node, callback) {
583
577
  if (type === "comap") {
584
578
  if (isBrowserImage(snapshot)) {
585
579
  extendedType = "image";
586
- } else if (isAccount(snapshot)) {
580
+ } else if (value.headerMeta?.type === "account") {
587
581
  extendedType = "account";
588
582
  } else if (value.core.isGroup()) {
589
583
  extendedType = "group";
@@ -1428,6 +1422,11 @@ function Grid(props) {
1428
1422
  }
1429
1423
  }
1430
1424
 
1425
+ // src/inspector/utils/permissions.ts
1426
+ function isWriter(role) {
1427
+ return role === "writer" || role === "admin" || role === "manager" || role === "writeOnly";
1428
+ }
1429
+
1431
1430
  // src/inspector/viewer/grid-view.tsx
1432
1431
  import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs10 } from "react/jsx-runtime";
1433
1432
  function GridItem({
@@ -1502,7 +1501,7 @@ function GridItem({
1502
1501
  /* @__PURE__ */ jsx22(Text, { strong: true, children: key }),
1503
1502
  /* @__PURE__ */ jsx22(Badge, { children: /* @__PURE__ */ jsx22(ResolveIcon, { coId: value, node }) })
1504
1503
  ] }) : /* @__PURE__ */ jsx22(Text, { strong: true, children: key }) }),
1505
- coValue && /* @__PURE__ */ jsxs10(ActionButtons, { children: [
1504
+ coValue && isWriter(coValue.group.myRole()) && /* @__PURE__ */ jsxs10(ActionButtons, { children: [
1506
1505
  /* @__PURE__ */ jsx22(
1507
1506
  EditButton,
1508
1507
  {
@@ -2054,17 +2053,90 @@ function AccountView({
2054
2053
  }
2055
2054
 
2056
2055
  // src/inspector/viewer/co-plain-text-view.tsx
2056
+ import { useState as useState10 } from "react";
2057
+ import { styled as styled18 } from "goober";
2058
+ import { CoPlainText } from "jazz-tools";
2057
2059
  import { Fragment as Fragment7, jsx as jsx29, jsxs as jsxs16 } from "react/jsx-runtime";
2058
- function CoPlainTextView({ data }) {
2060
+ function CoPlainTextView({
2061
+ data,
2062
+ coValue
2063
+ }) {
2064
+ const currentText = Object.values(data).join("");
2065
+ const [isEditing, setIsEditing] = useState10(false);
2066
+ const [editValue, setEditValue] = useState10("");
2067
+ const canEdit2 = isWriter(coValue.group.myRole());
2068
+ const handleEditClick = () => {
2069
+ setIsEditing(true);
2070
+ setEditValue(currentText);
2071
+ };
2072
+ const handleCancel = () => {
2073
+ setIsEditing(false);
2074
+ setEditValue(currentText);
2075
+ };
2076
+ const handleSave = (e) => {
2077
+ e.preventDefault();
2078
+ e.stopPropagation();
2079
+ const coPlainText = CoPlainText.fromRaw(coValue);
2080
+ coPlainText.$jazz.applyDiff(editValue);
2081
+ setIsEditing(false);
2082
+ };
2059
2083
  if (!data) return;
2084
+ if (isEditing) {
2085
+ return /* @__PURE__ */ jsxs16(Fragment7, { children: [
2086
+ /* @__PURE__ */ jsxs16(EditForm2, { onSubmit: handleSave, children: [
2087
+ /* @__PURE__ */ jsx29(
2088
+ StyledTextarea2,
2089
+ {
2090
+ value: editValue,
2091
+ onChange: (e) => setEditValue(e.target.value),
2092
+ onClick: (e) => e.stopPropagation()
2093
+ }
2094
+ ),
2095
+ /* @__PURE__ */ jsxs16(FormActions2, { children: [
2096
+ /* @__PURE__ */ jsx29(Button, { type: "button", variant: "secondary", onClick: handleCancel, children: "Cancel" }),
2097
+ /* @__PURE__ */ jsx29(Button, { type: "submit", variant: "primary", children: "Save" })
2098
+ ] })
2099
+ ] }),
2100
+ /* @__PURE__ */ jsx29(RawDataCard, { data })
2101
+ ] });
2102
+ }
2060
2103
  return /* @__PURE__ */ jsxs16(Fragment7, { children: [
2061
- /* @__PURE__ */ jsx29("p", { children: Object.values(data).join("") }),
2104
+ /* @__PURE__ */ jsx29("p", { children: currentText }),
2105
+ /* @__PURE__ */ jsx29("div", { children: canEdit2 && /* @__PURE__ */ jsx29(Button, { variant: "secondary", onClick: handleEditClick, title: "Edit", children: /* @__PURE__ */ jsx29(Icon, { name: "edit" }) }) }),
2062
2106
  /* @__PURE__ */ jsx29(RawDataCard, { data })
2063
2107
  ] });
2064
2108
  }
2109
+ var EditForm2 = styled18("form")`
2110
+ display: flex;
2111
+ flex-direction: column;
2112
+ gap: 0.75rem;
2113
+ margin-bottom: 1rem;
2114
+ `;
2115
+ var StyledTextarea2 = styled18("textarea")`
2116
+ width: 100%;
2117
+ min-height: 120px;
2118
+ border-radius: var(--j-radius-md);
2119
+ border: 1px solid var(--j-border-color);
2120
+ padding: 0.5rem 0.875rem;
2121
+ box-shadow: var(--j-shadow-sm);
2122
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
2123
+ font-size: 0.875rem;
2124
+ background-color: white;
2125
+ color: var(--j-text-color-strong);
2126
+ resize: vertical;
2127
+
2128
+ @media (prefers-color-scheme: dark) {
2129
+ background-color: var(--j-foreground);
2130
+ }
2131
+ `;
2132
+ var FormActions2 = styled18("div")`
2133
+ display: flex;
2134
+ gap: 0.5rem;
2135
+ justify-content: flex-end;
2136
+ `;
2065
2137
 
2066
2138
  // src/inspector/viewer/group-view.tsx
2067
- import { useState as useState10 } from "react";
2139
+ import { useState as useState11 } from "react";
2068
2140
  import { Fragment as Fragment8, jsx as jsx30, jsxs as jsxs17 } from "react/jsx-runtime";
2069
2141
  function partitionMembers(data) {
2070
2142
  const everyone = Object.entries(data).filter(([key]) => key === "everyone").map(([key, value]) => ({
@@ -2093,7 +2165,7 @@ function GroupView({
2093
2165
  onNavigate,
2094
2166
  node
2095
2167
  }) {
2096
- const [addMemberType, setAddMemberType] = useState10(null);
2168
+ const [addMemberType, setAddMemberType] = useState11(null);
2097
2169
  const { everyone, members, parentGroups, childGroups } = partitionMembers(
2098
2170
  data
2099
2171
  );
@@ -2353,17 +2425,17 @@ function RoleDisplay({
2353
2425
  }
2354
2426
 
2355
2427
  // src/inspector/viewer/table-viewer.tsx
2356
- import { styled as styled18 } from "goober";
2357
- import { useMemo as useMemo2, useState as useState11 } from "react";
2428
+ import { styled as styled19 } from "goober";
2429
+ import { useMemo as useMemo2, useState as useState12 } from "react";
2358
2430
  import { Fragment as Fragment9, jsx as jsx31, jsxs as jsxs19 } from "react/jsx-runtime";
2359
- var PaginationContainer = styled18("div")`
2431
+ var PaginationContainer = styled19("div")`
2360
2432
  padding: 1rem 0;
2361
2433
  display: flex;
2362
2434
  align-items: center;
2363
2435
  justify-content: space-between;
2364
2436
  gap: 0.5rem;
2365
2437
  `;
2366
- var RedTooltip = styled18("span")`
2438
+ var RedTooltip = styled19("span")`
2367
2439
  position:relative; /* making the .tooltip span a container for the tooltip text */
2368
2440
  border-bottom:1px dashed #000; /* little indicater to indicate it's hoverable */
2369
2441
 
@@ -2400,7 +2472,7 @@ function CoValuesTableView({
2400
2472
  onNavigate,
2401
2473
  onRemove
2402
2474
  }) {
2403
- const [visibleRowsCount, setVisibleRowsCount] = useState11(10);
2475
+ const [visibleRowsCount, setVisibleRowsCount] = useState12(10);
2404
2476
  const [coIdArray, visibleRows] = useMemo2(() => {
2405
2477
  const coIdArray2 = Array.isArray(data) ? data : Object.values(data).every((k) => typeof k === "string" && isCoId(k)) ? Object.values(data).map((k) => k) : [];
2406
2478
  const visibleRows2 = coIdArray2.slice(0, visibleRowsCount);
@@ -2528,7 +2600,7 @@ function TableView({
2528
2600
 
2529
2601
  // src/inspector/viewer/history-view.tsx
2530
2602
  import { useMemo as useMemo3 } from "react";
2531
- import { styled as styled19 } from "goober";
2603
+ import { styled as styled20 } from "goober";
2532
2604
 
2533
2605
  // src/inspector/utils/transactions-changes.ts
2534
2606
  var isGroupExtension = (change) => {
@@ -2571,6 +2643,140 @@ var isStreamEnd = (change) => {
2571
2643
  return change?.type === "end";
2572
2644
  };
2573
2645
 
2646
+ // src/inspector/utils/history.ts
2647
+ import { stringifyOpID } from "cojson";
2648
+ function areSameOpIds(opId1, opId2) {
2649
+ if (typeof opId1 === "string" || typeof opId2 === "string") {
2650
+ return opId1 === opId2;
2651
+ }
2652
+ return opId1.sessionID === opId2.sessionID && opId1.txIndex === opId2.txIndex && opId1.changeIdx === opId2.changeIdx;
2653
+ }
2654
+ function isCoPlainText(coValue) {
2655
+ return coValue.type === "coplaintext";
2656
+ }
2657
+ function getTransactionChanges(tx, coValue) {
2658
+ if (tx.isValid === false && tx.tx.privacy === "private") {
2659
+ const readKey = coValue.core.getReadKey(tx.tx.keyUsed);
2660
+ if (!readKey) {
2661
+ return [
2662
+ `Unable to decrypt transaction: read key ${tx.tx.keyUsed} not found.`
2663
+ ];
2664
+ }
2665
+ return coValue.core.verified.decryptTransaction(
2666
+ tx.txID.sessionID,
2667
+ tx.txID.txIndex,
2668
+ readKey
2669
+ ) ?? [];
2670
+ }
2671
+ if (isCoPlainText(coValue)) {
2672
+ if (tx.changes === void 0 || tx.changes.length === 0) return [];
2673
+ const firstChange = tx.changes[0];
2674
+ if (isItemAppend(firstChange) && tx.changes.every(
2675
+ (c) => isItemAppend(c) && areSameOpIds(c.after, firstChange.after)
2676
+ )) {
2677
+ const changes = tx.changes;
2678
+ if (firstChange.after !== "start") {
2679
+ changes.reverse();
2680
+ }
2681
+ return [
2682
+ {
2683
+ op: "app",
2684
+ value: changes.map((c) => c.value).join(""),
2685
+ after: firstChange.after
2686
+ }
2687
+ ];
2688
+ }
2689
+ if (isItemPrepend(firstChange) && tx.changes.every(
2690
+ (c) => isItemPrepend(c) && areSameOpIds(c.before, firstChange.before)
2691
+ )) {
2692
+ const changes = tx.changes;
2693
+ if (firstChange.before !== "end") {
2694
+ changes.reverse();
2695
+ }
2696
+ return [
2697
+ {
2698
+ op: "pre",
2699
+ value: changes.map((c) => c.value).join(""),
2700
+ before: firstChange.before
2701
+ }
2702
+ ];
2703
+ }
2704
+ if (isItemDeletion(firstChange) && tx.changes.every((c) => isItemDeletion(c))) {
2705
+ let changesAreConsecutive2 = function(changes) {
2706
+ if (changes.length < 2) return false;
2707
+ const mapping = coValueBeforeDeletions.mapping.idxAfterOpID;
2708
+ for (let i = 1; i < changes.length; ++i) {
2709
+ const prevIdx = mapping[stringifyOpID(changes[i - 1].insertion)];
2710
+ const currIdx = mapping[stringifyOpID(changes[i].insertion)];
2711
+ if (currIdx !== prevIdx && currIdx !== (prevIdx ?? -2) + 1) {
2712
+ return false;
2713
+ }
2714
+ }
2715
+ return true;
2716
+ };
2717
+ var changesAreConsecutive = changesAreConsecutive2;
2718
+ const coValueBeforeDeletions = coValue.atTime(tx.madeAt - 1);
2719
+ if (changesAreConsecutive2(tx.changes)) {
2720
+ const groupedBySession = /* @__PURE__ */ new Map();
2721
+ for (const change of tx.changes) {
2722
+ const group = `${change.insertion.sessionID}-${change.insertion.txIndex}`;
2723
+ if (!groupedBySession.has(group)) groupedBySession.set(group, []);
2724
+ groupedBySession.get(group).push(change);
2725
+ }
2726
+ return Array.from(groupedBySession.values()).map((changes) => {
2727
+ const stringDeleted = changes.toSorted((a, b) => {
2728
+ if (a.insertion.txIndex === b.insertion.txIndex) {
2729
+ return a.insertion.changeIdx - b.insertion.changeIdx;
2730
+ }
2731
+ return a.insertion.txIndex - b.insertion.txIndex;
2732
+ }).map(
2733
+ (c) => coValueBeforeDeletions.get(
2734
+ coValueBeforeDeletions.mapping.idxAfterOpID[stringifyOpID(c.insertion)]
2735
+ )
2736
+ ).join("");
2737
+ return {
2738
+ op: "custom",
2739
+ action: `"${stringDeleted}" has been deleted`
2740
+ };
2741
+ });
2742
+ }
2743
+ }
2744
+ }
2745
+ return tx.changes ?? tx.tx.changes ?? [];
2746
+ }
2747
+ function restoreCoMapToTimestamp(coValue, timestamp, removeUnknownProperties) {
2748
+ const myRole = coValue.group.myRole();
2749
+ if (myRole === void 0 || !["admin", "manager", "writer", "writerOnly"].includes(myRole)) {
2750
+ return;
2751
+ }
2752
+ const newCoValue = coValue.atTime(timestamp).toJSON();
2753
+ const oldCoValue = coValue.toJSON();
2754
+ if (newCoValue === null) return;
2755
+ let changes = [];
2756
+ if (removeUnknownProperties) {
2757
+ for (const key in oldCoValue) {
2758
+ if (!(key in newCoValue)) {
2759
+ changes.push({
2760
+ op: "del",
2761
+ key
2762
+ });
2763
+ }
2764
+ }
2765
+ }
2766
+ for (const key in newCoValue) {
2767
+ if (newCoValue[key] !== oldCoValue[key]) {
2768
+ changes.push({
2769
+ op: "set",
2770
+ key,
2771
+ value: newCoValue[key]
2772
+ });
2773
+ }
2774
+ }
2775
+ if (changes.length > 0) {
2776
+ coValue.core.makeTransaction(changes, "private");
2777
+ }
2778
+ }
2779
+
2574
2780
  // src/inspector/viewer/history-view.tsx
2575
2781
  import { Fragment as Fragment10, jsx as jsx32, jsxs as jsxs20 } from "react/jsx-runtime";
2576
2782
  function HistoryView({
@@ -2651,20 +2857,6 @@ function HistoryView({
2651
2857
  }
2652
2858
  ) });
2653
2859
  }
2654
- function getTransactionChanges(tx, coValue) {
2655
- if (tx.isValid === false && tx.tx.privacy === "private") {
2656
- const readKey = coValue.core.getReadKey(tx.tx.keyUsed);
2657
- if (!readKey) {
2658
- throw new Error("Read key not found");
2659
- }
2660
- return coValue.core.verified.decryptTransaction(
2661
- tx.txID.sessionID,
2662
- tx.txID.txIndex,
2663
- readKey
2664
- ) ?? [];
2665
- }
2666
- return tx.changes ?? tx.tx.changes ?? [];
2667
- }
2668
2860
  function getHistory(coValue) {
2669
2861
  return coValue.core.verifiedTransactions.flatMap((tx, index) => {
2670
2862
  const changes = getTransactionChanges(tx, coValue);
@@ -2743,6 +2935,9 @@ function mapTransactionToAction(change, coValue) {
2743
2935
  if (isPropertyDeletion(change)) {
2744
2936
  return `Property "${change.key}" has been deleted`;
2745
2937
  }
2938
+ if (change.op === "custom") {
2939
+ return change.action;
2940
+ }
2746
2941
  return "Unknown action: " + JSON.stringify(change);
2747
2942
  }
2748
2943
  var findListChange = (opId, coValue) => {
@@ -2750,7 +2945,7 @@ var findListChange = (opId, coValue) => {
2750
2945
  (tx) => tx.txID.sessionID === opId.sessionID && tx.txID.txIndex === opId.txIndex
2751
2946
  )?.changes?.[opId.changeIdx];
2752
2947
  };
2753
- var RedTooltip2 = styled19("span")`
2948
+ var RedTooltip2 = styled20("span")`
2754
2949
  position:relative; /* making the .tooltip span a container for the tooltip text */
2755
2950
  border-bottom:1px dashed #000; /* little indicater to indicate it's hoverable */
2756
2951
 
@@ -2783,44 +2978,8 @@ var RedTooltip2 = styled19("span")`
2783
2978
  `;
2784
2979
 
2785
2980
  // src/inspector/viewer/co-map-view.tsx
2786
- import { useState as useState12, useMemo as useMemo4 } from "react";
2787
- import { styled as styled20 } from "goober";
2788
-
2789
- // src/inspector/utils/history.ts
2790
- function restoreCoMapToTimestamp(coValue, timestamp, removeUnknownProperties) {
2791
- const myRole = coValue.group.myRole();
2792
- if (myRole === void 0 || !["admin", "manager", "writer", "writerOnly"].includes(myRole)) {
2793
- return;
2794
- }
2795
- const newCoValue = coValue.atTime(timestamp).toJSON();
2796
- const oldCoValue = coValue.toJSON();
2797
- if (newCoValue === null) return;
2798
- let changes = [];
2799
- if (removeUnknownProperties) {
2800
- for (const key in oldCoValue) {
2801
- if (!(key in newCoValue)) {
2802
- changes.push({
2803
- op: "del",
2804
- key
2805
- });
2806
- }
2807
- }
2808
- }
2809
- for (const key in newCoValue) {
2810
- if (newCoValue[key] !== oldCoValue[key]) {
2811
- changes.push({
2812
- op: "set",
2813
- key,
2814
- value: newCoValue[key]
2815
- });
2816
- }
2817
- }
2818
- if (changes.length > 0) {
2819
- coValue.core.makeTransaction(changes, "private");
2820
- }
2821
- }
2822
-
2823
- // src/inspector/viewer/co-map-view.tsx
2981
+ import { useState as useState13, useMemo as useMemo4 } from "react";
2982
+ import { styled as styled21 } from "goober";
2824
2983
  import { Fragment as Fragment11, jsx as jsx33, jsxs as jsxs21 } from "react/jsx-runtime";
2825
2984
  function CoMapView({
2826
2985
  coValue,
@@ -2839,7 +2998,14 @@ function CoMapView({
2839
2998
  }
2840
2999
  ),
2841
3000
  /* @__PURE__ */ jsxs21("div", { children: [
2842
- /* @__PURE__ */ jsx33(AddPropertyModal, { coValue, node }),
3001
+ /* @__PURE__ */ jsx33(
3002
+ AddPropertyModal,
3003
+ {
3004
+ disabled: !isWriter(coValue.group.myRole()),
3005
+ coValue,
3006
+ node
3007
+ }
3008
+ ),
2843
3009
  " ",
2844
3010
  /* @__PURE__ */ jsx33(RestoreSnapshotModal, { coValue })
2845
3011
  ] })
@@ -2847,10 +3013,11 @@ function CoMapView({
2847
3013
  }
2848
3014
  function AddPropertyModal({
2849
3015
  coValue,
2850
- node
3016
+ node,
3017
+ disabled
2851
3018
  }) {
2852
- const [isAddPropertyModalOpen, setIsAddPropertyModalOpen] = useState12(false);
2853
- const [propertyName, setPropertyName] = useState12("");
3019
+ const [isAddPropertyModalOpen, setIsAddPropertyModalOpen] = useState13(false);
3020
+ const [propertyName, setPropertyName] = useState13("");
2854
3021
  const openAddPropertyModal = () => {
2855
3022
  setIsAddPropertyModalOpen(true);
2856
3023
  setPropertyName("");
@@ -2865,8 +3032,9 @@ function AddPropertyModal({
2865
3032
  {
2866
3033
  title: "Add Property",
2867
3034
  variant: "secondary",
3035
+ disabled,
2868
3036
  onClick: openAddPropertyModal,
2869
- children: /* @__PURE__ */ jsx33(Icon, { name: "edit" })
3037
+ children: /* @__PURE__ */ jsx33(Icon, { name: "add" })
2870
3038
  }
2871
3039
  ),
2872
3040
  /* @__PURE__ */ jsxs21(
@@ -2902,9 +3070,9 @@ function AddPropertyModal({
2902
3070
  ] });
2903
3071
  }
2904
3072
  function RestoreSnapshotModal({ coValue }) {
2905
- const [isRestoreModalOpen, setIsRestoreModalOpen] = useState12(false);
2906
- const [selectedIndex, setSelectedIndex] = useState12(-1);
2907
- const [removeUnknownProperties, setRemoveUnknownProperties] = useState12(false);
3073
+ const [isRestoreModalOpen, setIsRestoreModalOpen] = useState13(false);
3074
+ const [selectedIndex, setSelectedIndex] = useState13(-1);
3075
+ const [removeUnknownProperties, setRemoveUnknownProperties] = useState13(false);
2908
3076
  const timestamps = useMemo4(
2909
3077
  () => coValue.core.verifiedTransactions.map((tx) => tx.madeAt),
2910
3078
  [coValue.core.verifiedTransactions.length]
@@ -2932,6 +3100,7 @@ function RestoreSnapshotModal({ coValue }) {
2932
3100
  const handleClose = () => {
2933
3101
  setIsRestoreModalOpen(false);
2934
3102
  };
3103
+ const canRestore = isWriter(coValue.group.myRole());
2935
3104
  return /* @__PURE__ */ jsxs21(Fragment11, { children: [
2936
3105
  /* @__PURE__ */ jsx33(Button, { title: "Timeline", variant: "secondary", onClick: openRestoreModal, children: /* @__PURE__ */ jsx33(Icon, { name: "history" }) }),
2937
3106
  /* @__PURE__ */ jsxs21(
@@ -2944,7 +3113,7 @@ function RestoreSnapshotModal({ coValue }) {
2944
3113
  cancelText: "Cancel",
2945
3114
  onConfirm: handleRestore,
2946
3115
  onCancel: handleClose,
2947
- showButtons: timestamps.length > 1,
3116
+ showButtons: timestamps.length > 1 && canRestore,
2948
3117
  children: [
2949
3118
  timestamps.length > 1 && /* @__PURE__ */ jsxs21(Fragment11, { children: [
2950
3119
  /* @__PURE__ */ jsxs21(RangeContainer, { children: [
@@ -2962,7 +3131,7 @@ function RestoreSnapshotModal({ coValue }) {
2962
3131
  ),
2963
3132
  /* @__PURE__ */ jsx33(TimestampDisplay, { children: timestamps[selectedIndex] !== void 0 ? new Date(timestamps[selectedIndex]).toISOString() : "No timestamps available" })
2964
3133
  ] }),
2965
- /* @__PURE__ */ jsxs21(CheckboxContainer, { children: [
3134
+ canRestore && /* @__PURE__ */ jsxs21(CheckboxContainer, { children: [
2966
3135
  /* @__PURE__ */ jsx33(
2967
3136
  CheckboxInput,
2968
3137
  {
@@ -2985,15 +3154,15 @@ function RestoreSnapshotModal({ coValue }) {
2985
3154
  )
2986
3155
  ] });
2987
3156
  }
2988
- var PreviewSection = styled20("div")`
3157
+ var PreviewSection = styled21("div")`
2989
3158
  margin-top: 1.5rem;
2990
3159
  `;
2991
- var PreviewLabel = styled20("div")`
3160
+ var PreviewLabel = styled21("div")`
2992
3161
  font-weight: 500;
2993
3162
  margin-bottom: 0.5rem;
2994
3163
  color: var(--j-text-color-strong);
2995
3164
  `;
2996
- var PreviewPre = styled20("pre")`
3165
+ var PreviewPre = styled21("pre")`
2997
3166
  background-color: var(--j-foreground);
2998
3167
  border: 1px solid var(--j-border-color);
2999
3168
  border-radius: var(--j-radius-md);
@@ -3005,17 +3174,17 @@ var PreviewPre = styled20("pre")`
3005
3174
  color: var(--j-text-color);
3006
3175
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
3007
3176
  `;
3008
- var RangeContainer = styled20("div")`
3177
+ var RangeContainer = styled21("div")`
3009
3178
  display: flex;
3010
3179
  flex-direction: column;
3011
3180
  gap: 0.75rem;
3012
3181
  `;
3013
- var RangeLabel = styled20("label")`
3182
+ var RangeLabel = styled21("label")`
3014
3183
  font-weight: 500;
3015
3184
  color: var(--j-text-color-strong);
3016
3185
  font-size: 0.875rem;
3017
3186
  `;
3018
- var RangeInput = styled20("input")`
3187
+ var RangeInput = styled21("input")`
3019
3188
  width: 100%;
3020
3189
  height: 0.5rem;
3021
3190
  border-radius: var(--j-radius-sm);
@@ -3052,7 +3221,7 @@ var RangeInput = styled20("input")`
3052
3221
  cursor: not-allowed;
3053
3222
  }
3054
3223
  `;
3055
- var TimestampDisplay = styled20("div")`
3224
+ var TimestampDisplay = styled21("div")`
3056
3225
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
3057
3226
  font-size: 0.875rem;
3058
3227
  color: var(--j-text-color);
@@ -3062,26 +3231,26 @@ var TimestampDisplay = styled20("div")`
3062
3231
  border-radius: var(--j-radius-md);
3063
3232
  text-align: center;
3064
3233
  `;
3065
- var CheckboxContainer = styled20("div")`
3234
+ var CheckboxContainer = styled21("div")`
3066
3235
  display: flex;
3067
3236
  align-items: flex-start;
3068
3237
  gap: 0.5rem;
3069
3238
  margin-top: 1rem;
3070
3239
  `;
3071
- var CheckboxInput = styled20("input")`
3240
+ var CheckboxInput = styled21("input")`
3072
3241
  width: 1rem;
3073
3242
  height: 1rem;
3074
3243
  margin-top: 0.125rem;
3075
3244
  cursor: pointer;
3076
3245
  accent-color: var(--j-primary-color);
3077
3246
  `;
3078
- var CheckboxLabel = styled20("label")`
3247
+ var CheckboxLabel = styled21("label")`
3079
3248
  font-size: 0.875rem;
3080
3249
  color: var(--j-text-color);
3081
3250
  cursor: pointer;
3082
3251
  line-height: 1.25rem;
3083
3252
  `;
3084
- var EditorContainer = styled20("div")`
3253
+ var EditorContainer = styled21("div")`
3085
3254
  margin-top: 1rem;
3086
3255
  `;
3087
3256
 
@@ -3090,7 +3259,7 @@ import { Fragment as Fragment12, jsx as jsx34, jsxs as jsxs22 } from "react/jsx-
3090
3259
  var BasePageContainer = React5.forwardRef(
3091
3260
  ({ isTopLevel, ...rest }, ref) => /* @__PURE__ */ jsx34("div", { ref, ...rest })
3092
3261
  );
3093
- var PageContainer = styled21(BasePageContainer)`
3262
+ var PageContainer = styled22(BasePageContainer)`
3094
3263
  position: absolute;
3095
3264
  z-index: 10;
3096
3265
  inset: 0;
@@ -3098,36 +3267,36 @@ var PageContainer = styled21(BasePageContainer)`
3098
3267
  height: 100%;
3099
3268
  padding: 0 0.75rem;
3100
3269
  `;
3101
- var BackButton = styled21("div")`
3270
+ var BackButton = styled22("div")`
3102
3271
  position: absolute;
3103
3272
  left: 0;
3104
3273
  right: 0;
3105
3274
  top: 0;
3106
3275
  height: 2.5rem;
3107
3276
  `;
3108
- var HeaderContainer = styled21("div")`
3277
+ var HeaderContainer = styled22("div")`
3109
3278
  display: flex;
3110
3279
  justify-content: space-between;
3111
3280
  align-items: center;
3112
3281
  margin-bottom: 1rem;
3113
3282
  `;
3114
- var TitleContainer = styled21("div")`
3283
+ var TitleContainer = styled22("div")`
3115
3284
  display: flex;
3116
3285
  align-items: center;
3117
3286
  gap: 0.75rem;
3118
3287
  `;
3119
- var Title = styled21(Heading)`
3288
+ var Title = styled22(Heading)`
3120
3289
  display: flex;
3121
3290
  flex-direction: column;
3122
3291
  align-items: flex-start;
3123
3292
  gap: 0.25rem;
3124
3293
  `;
3125
- var BadgeContainer = styled21("div")`
3294
+ var BadgeContainer = styled22("div")`
3126
3295
  display: flex;
3127
3296
  align-items: center;
3128
3297
  gap: 0.75rem;
3129
3298
  `;
3130
- var ContentContainer = styled21("div")`
3299
+ var ContentContainer = styled22("div")`
3131
3300
  overflow: auto;
3132
3301
  display: flex;
3133
3302
  flex-direction: column;
@@ -3173,7 +3342,14 @@ function View(props) {
3173
3342
  return /* @__PURE__ */ jsx34(AccountView, { data: snapshot, node, onNavigate });
3174
3343
  }
3175
3344
  if (type === "coplaintext") {
3176
- return /* @__PURE__ */ jsx34(CoPlainTextView, { data: snapshot });
3345
+ return /* @__PURE__ */ jsx34(
3346
+ CoPlainTextView,
3347
+ {
3348
+ data: snapshot,
3349
+ coValue: value,
3350
+ node
3351
+ }
3352
+ );
3177
3353
  }
3178
3354
  if (type === "colist") {
3179
3355
  const handleRemove = (index) => {
@@ -3278,7 +3454,7 @@ function Page(props) {
3278
3454
 
3279
3455
  // src/inspector/ui/error-boundary.tsx
3280
3456
  import React6 from "react";
3281
- import { styled as styled22 } from "goober";
3457
+ import { styled as styled23 } from "goober";
3282
3458
  import { jsx as jsx35, jsxs as jsxs23 } from "react/jsx-runtime";
3283
3459
  var ErrorBoundary = class extends React6.Component {
3284
3460
  constructor(props) {
@@ -3302,7 +3478,7 @@ var ErrorBoundary = class extends React6.Component {
3302
3478
  return this.props.children;
3303
3479
  }
3304
3480
  };
3305
- var StyledHeading2 = styled22("h1")`
3481
+ var StyledHeading2 = styled23("h1")`
3306
3482
  font-size: 1.125rem;
3307
3483
  font-weight: 500;
3308
3484
  color: var(--j-text-color-strong);
@@ -3310,7 +3486,7 @@ var StyledHeading2 = styled22("h1")`
3310
3486
 
3311
3487
  // src/inspector/viewer/page-stack.tsx
3312
3488
  import { Fragment as Fragment13, jsx as jsx36, jsxs as jsxs24 } from "react/jsx-runtime";
3313
- var PageStackContainer = styled23("div")`
3489
+ var PageStackContainer = styled24("div")`
3314
3490
  position: relative;
3315
3491
  padding: 0 0.75rem;
3316
3492
  overflow-y: auto;
@@ -3344,10 +3520,10 @@ function PageStack({
3344
3520
  }
3345
3521
 
3346
3522
  // src/inspector/viewer/use-page-path.ts
3347
- import { useCallback, useEffect as useEffect8, useState as useState13 } from "react";
3523
+ import { useCallback, useEffect as useEffect8, useState as useState14 } from "react";
3348
3524
  var STORAGE_KEY = "jazz-inspector-paths";
3349
3525
  function usePagePath(defaultPath) {
3350
- const [path, setPath] = useState13(() => {
3526
+ const [path, setPath] = useState14(() => {
3351
3527
  if (typeof window === "undefined") return [];
3352
3528
  const stored = localStorage.getItem(STORAGE_KEY);
3353
3529
  if (stored) {
@@ -3401,8 +3577,8 @@ function usePagePath(defaultPath) {
3401
3577
  }
3402
3578
 
3403
3579
  // src/inspector/ui/global-styles.tsx
3404
- import { styled as styled24 } from "goober";
3405
- var GlobalStyles = styled24("div")`
3580
+ import { styled as styled25 } from "goober";
3581
+ var GlobalStyles = styled25("div")`
3406
3582
  /* Colors */
3407
3583
  --j-primary-color: #146AFF;
3408
3584
  --j-link-color: var(--j-primary-color);
@@ -3476,9 +3652,9 @@ var GlobalStyles = styled24("div")`
3476
3652
  `;
3477
3653
 
3478
3654
  // src/inspector/viewer/inspector-button.tsx
3479
- import { styled as styled25 } from "goober";
3655
+ import { styled as styled26 } from "goober";
3480
3656
  import { jsx as jsx37, jsxs as jsxs25 } from "react/jsx-runtime";
3481
- var StyledInspectorButton = styled25("button")`
3657
+ var StyledInspectorButton = styled26("button")`
3482
3658
  position: fixed;
3483
3659
  width: 2.5rem;
3484
3660
  height: 2.5rem;
@@ -3509,7 +3685,7 @@ var StyledInspectorButton = styled25("button")`
3509
3685
  }
3510
3686
  }}
3511
3687
  `;
3512
- var JazzIcon = styled25("svg")`
3688
+ var JazzIcon = styled26("svg")`
3513
3689
  width: 100%;
3514
3690
  height: auto;
3515
3691
  position: relative;
@@ -3561,10 +3737,10 @@ function InspectorButton({
3561
3737
  }
3562
3738
 
3563
3739
  // src/inspector/viewer/use-open-inspector.ts
3564
- import { useEffect as useEffect9, useState as useState14 } from "react";
3740
+ import { useEffect as useEffect9, useState as useState15 } from "react";
3565
3741
  var STORAGE_KEY2 = "jazz-inspector-open";
3566
3742
  function useOpenInspector() {
3567
- const [open, setOpen] = useState14(() => {
3743
+ const [open, setOpen] = useState15(() => {
3568
3744
  if (typeof window === "undefined") return false;
3569
3745
  const stored = localStorage.getItem(STORAGE_KEY2);
3570
3746
  return stored ? JSON.parse(stored) : false;
@@ -3576,12 +3752,12 @@ function useOpenInspector() {
3576
3752
  }
3577
3753
 
3578
3754
  // src/inspector/viewer/delete-local-data.tsx
3579
- import { useState as useState15 } from "react";
3755
+ import { useState as useState16 } from "react";
3580
3756
  import { Fragment as Fragment14, jsx as jsx38, jsxs as jsxs26 } from "react/jsx-runtime";
3581
3757
  var DELETE_LOCAL_DATA_STRING = "delete my local data";
3582
3758
  function DeleteLocalData() {
3583
- const [showDeleteModal, setShowDeleteModal] = useState15(false);
3584
- const [confirmDeleteString, setConfirmDeleteString] = useState15("");
3759
+ const [showDeleteModal, setShowDeleteModal] = useState16(false);
3760
+ const [confirmDeleteString, setConfirmDeleteString] = useState16("");
3585
3761
  return /* @__PURE__ */ jsxs26(Fragment14, { children: [
3586
3762
  /* @__PURE__ */ jsx38(Button, { variant: "destructive", onClick: () => setShowDeleteModal(true), children: "Delete my local data" }),
3587
3763
  /* @__PURE__ */ jsxs26(
@@ -3696,7 +3872,7 @@ function DeleteLocalData() {
3696
3872
 
3697
3873
  // src/inspector/viewer/new-app.tsx
3698
3874
  import { Fragment as Fragment15, jsx as jsx39, jsxs as jsxs27 } from "react/jsx-runtime";
3699
- var InspectorContainer = styled26("div")`
3875
+ var InspectorContainer = styled27("div")`
3700
3876
  position: fixed;
3701
3877
  height: 50vh;
3702
3878
  max-height: 800px;
@@ -3713,17 +3889,17 @@ var InspectorContainer = styled26("div")`
3713
3889
  background-color: var(--j-background);
3714
3890
  }
3715
3891
  `;
3716
- var HeaderContainer2 = styled26("div")`
3892
+ var HeaderContainer2 = styled27("div")`
3717
3893
  display: flex;
3718
3894
  align-items: center;
3719
3895
  gap: 1rem;
3720
3896
  padding: 0 0.75rem;
3721
3897
  margin: 0.75rem 0;
3722
3898
  `;
3723
- var Form = styled26("form")`
3899
+ var Form = styled27("form")`
3724
3900
  width: 24rem;
3725
3901
  `;
3726
- var InitialForm = styled26("form")`
3902
+ var InitialForm = styled27("form")`
3727
3903
  display: flex;
3728
3904
  flex-direction: column;
3729
3905
  position: relative;
@@ -3735,7 +3911,7 @@ var InitialForm = styled26("form")`
3735
3911
  max-width: 24rem;
3736
3912
  margin: 0 auto;
3737
3913
  `;
3738
- var OrText = styled26("p")`
3914
+ var OrText = styled27("p")`
3739
3915
  text-align: center;
3740
3916
  `;
3741
3917
  function JazzInspectorInternal({
@@ -3744,7 +3920,7 @@ function JazzInspectorInternal({
3744
3920
  accountId
3745
3921
  }) {
3746
3922
  const [open, setOpen] = useOpenInspector();
3747
- const [coValueId, setCoValueId] = useState16("");
3923
+ const [coValueId, setCoValueId] = useState17("");
3748
3924
  const { path, addPages, goToIndex, goBack, setPage } = usePagePath();
3749
3925
  const handleCoValueIdSubmit = (e) => {
3750
3926
  e.preventDefault();