@sanity/ailf-studio 0.1.25 → 0.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -7,22 +7,24 @@ import { DocumentRef } from './document-ref.js';
7
7
  * actions/GraduateToNativeAction.tsx
8
8
  *
9
9
  * Sanity Studio document action that "graduates" a mirrored task to
10
- * a native task by removing the `origin` field.
10
+ * a native (Studio-owned) task by changing the `ownership` field from
11
+ * "repo" to "studio".
11
12
  *
12
- * This is a one-way, irreversible operation. Once graduated:
13
+ * This is a one-way operation. Once graduated:
13
14
  * - The task becomes fully editable in Studio
14
- * - Future pipeline mirror syncs will NOT overwrite it (the mirror
15
- * uses createOrReplace with the same _id, but since origin is gone,
16
- * the document-level readOnly check returns false and the task
17
- * behaves like any other native task)
15
+ * - Future pipeline mirror syncs will skip this document (the mirror
16
+ * step checks `ownership` and skips documents with `ownership: "studio"`)
17
+ * - The `origin` field is preserved as provenance — you can always
18
+ * see where the task originally came from
18
19
  * - The task's _id is unchanged — it keeps the mirror prefix
19
20
  * (ailf.task.mirror.*) but that's just an ID string, not a
20
21
  * behavioral marker
21
22
  *
22
- * The action only appears on ailf.task documents that have an `origin`
23
- * field (i.e., mirrored tasks). Native tasks never see it.
23
+ * The action only appears on ailf.task documents that have
24
+ * `ownership: "repo"` (i.e., active mirrors). Native tasks and
25
+ * already-graduated tasks never see it.
24
26
  *
25
- * @see docs/exec-plans/tasks-as-content/phase-5-content-lake-mirroring.md
27
+ * @see docs/exec-plans/task-lifecycle/phase-1-ownership.md
26
28
  */
27
29
 
28
30
  declare const GraduateToNativeAction: DocumentActionComponent;
@@ -75,15 +77,29 @@ declare function ReleasePicker(props: StringInputProps): react_jsx_runtime.JSX.E
75
77
  /**
76
78
  * components/MirrorBanner.tsx
77
79
  *
78
- * Informational banner shown at the top of mirrored task documents.
79
- * Communicates that the task is managed in an external repo and links
80
- * to the source file on GitHub.
80
+ * Informational banner shown at the top of task documents that have
81
+ * origin provenance (both active mirrors and graduated tasks).
81
82
  *
82
- * Paired with `SyncStatusBadge` to show both the source and freshness
83
- * of the mirror.
83
+ * For active mirrors (`ownership: "repo"`): communicates that the task
84
+ * is managed in an external repo and links to the source file on GitHub.
84
85
  *
85
- * @see docs/exec-plans/tasks-as-content/phase-5-content-lake-mirroring.md
86
+ * For graduated tasks (`ownership: "studio"` with origin): shows
87
+ * provenance info about where the task originally came from.
88
+ *
89
+ * Paired with `SyncStatusBadge` to show sync freshness for active mirrors.
90
+ *
91
+ * @see docs/exec-plans/task-lifecycle/phase-1-ownership.md
86
92
  */
93
+ interface GitAuthorInfo {
94
+ gitName?: string;
95
+ gitEmail?: string;
96
+ githubUsername?: string;
97
+ }
98
+ interface GraduatedByInfo {
99
+ sanityId?: string;
100
+ name?: string;
101
+ email?: string;
102
+ }
87
103
  interface MirrorBannerProps {
88
104
  origin: {
89
105
  repo?: string;
@@ -93,9 +109,15 @@ interface MirrorBannerProps {
93
109
  branch?: string;
94
110
  commitSha?: string;
95
111
  lastSyncedAt?: string;
112
+ author?: GitAuthorInfo;
113
+ lastEditor?: GitAuthorInfo;
114
+ graduatedAt?: string;
115
+ graduatedBy?: GraduatedByInfo;
96
116
  };
117
+ /** Task ownership — "repo" for active mirrors, "studio" for graduated */
118
+ ownership?: string;
97
119
  }
98
- declare function MirrorBanner({ origin }: MirrorBannerProps): react_jsx_runtime.JSX.Element;
120
+ declare function MirrorBanner({ origin, ownership }: MirrorBannerProps): react_jsx_runtime.JSX.Element;
99
121
 
100
122
  /**
101
123
  * components/SyncStatusBadge.tsx
@@ -468,6 +490,8 @@ declare const taskSchema: {
468
490
  description: string;
469
491
  id: string;
470
492
  origin: string;
493
+ ownership: string;
494
+ status: string;
471
495
  }, Record<string, unknown>> | undefined;
472
496
  };
473
497
 
@@ -551,6 +575,7 @@ interface ComparisonData {
551
575
  generatedAt: string;
552
576
  improved: string[];
553
577
  noiseThreshold: number;
578
+ notEvaluated: string[];
554
579
  regressed: string[];
555
580
  unchanged: string[];
556
581
  }
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { definePlugin } from "sanity";
5
5
  import { EditIcon } from "@sanity/icons";
6
6
  import { Box, Stack, Text } from "@sanity/ui";
7
7
  import { useCallback, useState } from "react";
8
- import { useClient } from "sanity";
8
+ import { useClient, useCurrentUser } from "sanity";
9
9
 
10
10
  // src/lib/constants.ts
11
11
  var API_VERSION = "2026-03-11";
@@ -15,6 +15,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
15
15
  var GraduateToNativeAction = (props) => {
16
16
  const { id, type, draft, published, onComplete } = props;
17
17
  const client = useClient({ apiVersion: API_VERSION });
18
+ const currentUser = useCurrentUser();
18
19
  const [isConfirming, setIsConfirming] = useState(false);
19
20
  const doc = draft ?? published;
20
21
  const origin = doc?.origin;
@@ -23,17 +24,35 @@ var GraduateToNativeAction = (props) => {
23
24
  const handleGraduate = useCallback(async () => {
24
25
  try {
25
26
  const publishedId = id.replace(/^drafts\./, "");
26
- await client.patch(publishedId).unset(["origin"]).commit();
27
+ const graduationPatch = {
28
+ ownership: "studio",
29
+ "origin.graduatedAt": (/* @__PURE__ */ new Date()).toISOString(),
30
+ "origin.graduatedBy": {
31
+ sanityId: currentUser?.id ?? "unknown",
32
+ name: currentUser?.name ?? "unknown",
33
+ email: currentUser?.email ?? "unknown"
34
+ }
35
+ };
36
+ await client.patch(publishedId).set(graduationPatch).commit();
27
37
  if (draft) {
28
- await client.patch(`drafts.${publishedId}`).unset(["origin"]).commit().catch(() => {
38
+ await client.patch(`drafts.${publishedId}`).set(graduationPatch).commit().catch(() => {
29
39
  });
30
40
  }
31
41
  onComplete();
32
42
  } catch (err) {
33
43
  console.error("Failed to graduate task:", err);
34
44
  }
35
- }, [client, draft, id, onComplete]);
36
- if (type !== "ailf.task" || !origin) return null;
45
+ }, [
46
+ client,
47
+ currentUser?.email,
48
+ currentUser?.id,
49
+ currentUser?.name,
50
+ draft,
51
+ id,
52
+ onComplete
53
+ ]);
54
+ const ownership = doc?.ownership;
55
+ if (type !== "ailf.task" || ownership !== "repo") return null;
37
56
  return {
38
57
  dialog: isConfirming ? {
39
58
  type: "confirm",
@@ -44,8 +63,8 @@ var GraduateToNativeAction = (props) => {
44
63
  /* @__PURE__ */ jsx("strong", { children: repoDisplay }),
45
64
  " and make it fully editable in Studio."
46
65
  ] }),
47
- /* @__PURE__ */ jsx(Text, { weight: "semibold", children: "\u26A0\uFE0F This is a one-way operation. The task will no longer sync from the repo. Future pipeline runs will not overwrite your changes." }),
48
- repoUrl && /* @__PURE__ */ jsxs(Text, { size: 1, muted: true, children: [
66
+ /* @__PURE__ */ jsx(Text, { weight: "semibold", children: "\u26A0\uFE0F This is a one-way operation. The task will no longer sync from the repo \u2014 future mirror syncs will not overwrite your edits. The task will continue to run in evaluations. Source repo provenance is preserved for reference." }),
67
+ repoUrl && origin && /* @__PURE__ */ jsxs(Text, { size: 1, muted: true, children: [
49
68
  "Current source:",
50
69
  " ",
51
70
  /* @__PURE__ */ jsx("a", { href: repoUrl, target: "_blank", rel: "noopener noreferrer", children: origin.path }),
@@ -62,7 +81,7 @@ var GraduateToNativeAction = (props) => {
62
81
  icon: EditIcon,
63
82
  label: "Graduate to native task",
64
83
  onHandle: () => setIsConfirming(true),
65
- title: "Disconnect this task from its source repo so it becomes fully editable in Studio. This cannot be undone.",
84
+ title: "Transfer ownership to Studio so this task becomes fully editable. Source repo provenance is preserved.",
66
85
  tone: "caution"
67
86
  };
68
87
  };
@@ -74,7 +93,7 @@ import { useToast } from "@sanity/ui";
74
93
  import { useCallback as useCallback2, useEffect, useRef, useState as useState2 } from "react";
75
94
  import {
76
95
  useClient as useClient2,
77
- useCurrentUser,
96
+ useCurrentUser as useCurrentUser2,
78
97
  useDataset,
79
98
  useProjectId
80
99
  } from "sanity";
@@ -184,7 +203,7 @@ var RunTaskEvaluationAction = (props) => {
184
203
  const client = useClient2({ apiVersion: API_VERSION });
185
204
  const dataset = useDataset();
186
205
  const projectId = useProjectId();
187
- const currentUser = useCurrentUser();
206
+ const currentUser = useCurrentUser2();
188
207
  const toast = useToast();
189
208
  const [state, setState] = useState2({ status: "idle" });
190
209
  const requestedAtRef = useRef(null);
@@ -1537,6 +1556,13 @@ var reportSchema = defineType4({
1537
1556
  title: "Unchanged Areas",
1538
1557
  type: "array"
1539
1558
  }),
1559
+ defineField4({
1560
+ name: "notEvaluated",
1561
+ of: [{ type: "string" }],
1562
+ title: "Not Evaluated Areas",
1563
+ description: "Areas present in only one of the two runs (e.g., auto-scoped release eval vs full baseline).",
1564
+ type: "array"
1565
+ }),
1540
1566
  defineField4({
1541
1567
  name: "noiseThreshold",
1542
1568
  title: "Noise Threshold",
@@ -1872,6 +1898,7 @@ function CanonicalDocPreview(props) {
1872
1898
 
1873
1899
  // src/components/OriginInput.tsx
1874
1900
  import { Stack as Stack5 } from "@sanity/ui";
1901
+ import { useFormValue } from "sanity";
1875
1902
 
1876
1903
  // src/components/MirrorBanner.tsx
1877
1904
  import { LinkIcon } from "@sanity/icons";
@@ -1918,32 +1945,45 @@ function SyncStatusBadge({
1918
1945
  }
1919
1946
 
1920
1947
  // src/components/MirrorBanner.tsx
1921
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1922
- function MirrorBanner({ origin }) {
1948
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1949
+ function formatAuthor(author) {
1950
+ if (!author) return null;
1951
+ const parts = [];
1952
+ if (author.gitName) parts.push(author.gitName);
1953
+ if (author.githubUsername) parts.push(`(${author.githubUsername})`);
1954
+ if (parts.length === 0 && author.gitEmail) parts.push(author.gitEmail);
1955
+ return parts.length > 0 ? parts.join(" ") : null;
1956
+ }
1957
+ function MirrorBanner({ origin, ownership }) {
1923
1958
  const { repo, path, branch, commitSha, lastSyncedAt } = origin;
1959
+ const isGraduated = ownership === "studio";
1924
1960
  const repoUrl = repo && path && branch ? `https://github.com/${repo}/blob/${branch}/${path}` : null;
1925
1961
  const repoDisplay = repo ?? "an external repository";
1962
+ const repoLink = repoUrl ? /* @__PURE__ */ jsx6(
1963
+ "a",
1964
+ {
1965
+ href: repoUrl,
1966
+ target: "_blank",
1967
+ rel: "noopener noreferrer",
1968
+ style: { fontWeight: 600 },
1969
+ children: repoDisplay
1970
+ }
1971
+ ) : /* @__PURE__ */ jsx6("strong", { children: repoDisplay });
1926
1972
  return /* @__PURE__ */ jsx6(Card3, { padding: 3, radius: 2, tone: "transparent", border: true, children: /* @__PURE__ */ jsxs6(Stack4, { space: 3, children: [
1927
1973
  /* @__PURE__ */ jsxs6(Flex4, { align: "center", gap: 2, children: [
1928
1974
  /* @__PURE__ */ jsx6(Text6, { size: 2, children: /* @__PURE__ */ jsx6(LinkIcon, {}) }),
1929
- /* @__PURE__ */ jsxs6(Text6, { size: 2, children: [
1930
- "This task is managed in",
1931
- " ",
1932
- repoUrl ? /* @__PURE__ */ jsx6(
1933
- "a",
1934
- {
1935
- href: repoUrl,
1936
- target: "_blank",
1937
- rel: "noopener noreferrer",
1938
- style: { fontWeight: 600 },
1939
- children: repoDisplay
1940
- }
1941
- ) : /* @__PURE__ */ jsx6("strong", { children: repoDisplay }),
1975
+ /* @__PURE__ */ jsx6(Text6, { size: 2, children: isGraduated ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
1976
+ "This task was originally mirrored from ",
1977
+ repoLink,
1978
+ " and has been graduated to Studio ownership."
1979
+ ] }) : /* @__PURE__ */ jsxs6(Fragment3, { children: [
1980
+ "This task is managed in ",
1981
+ repoLink,
1942
1982
  ". Edit it there to make changes."
1943
- ] })
1983
+ ] }) })
1944
1984
  ] }),
1945
1985
  /* @__PURE__ */ jsxs6(Flex4, { align: "center", gap: 2, wrap: "wrap", children: [
1946
- lastSyncedAt && /* @__PURE__ */ jsx6(
1986
+ !isGraduated && lastSyncedAt && /* @__PURE__ */ jsx6(
1947
1987
  SyncStatusBadge,
1948
1988
  {
1949
1989
  lastSyncedAt,
@@ -1956,6 +1996,19 @@ function MirrorBanner({ origin }) {
1956
1996
  commitSha ? ` @ ${commitSha.slice(0, 7)}` : ""
1957
1997
  ] }),
1958
1998
  path && /* @__PURE__ */ jsx6(Text6, { size: 1, muted: true, children: path })
1999
+ ] }),
2000
+ isGraduated && origin.graduatedBy?.name && /* @__PURE__ */ jsxs6(Text6, { size: 1, muted: true, children: [
2001
+ "Graduated by ",
2002
+ origin.graduatedBy.name,
2003
+ origin.graduatedAt && ` \xB7 ${new Date(origin.graduatedAt).toLocaleDateString()}`
2004
+ ] }),
2005
+ !isGraduated && formatAuthor(origin.lastEditor) && /* @__PURE__ */ jsxs6(Text6, { size: 1, muted: true, children: [
2006
+ "Last edited by ",
2007
+ formatAuthor(origin.lastEditor)
2008
+ ] }),
2009
+ isGraduated && formatAuthor(origin.author) && /* @__PURE__ */ jsxs6(Text6, { size: 1, muted: true, children: [
2010
+ "Originally authored by ",
2011
+ formatAuthor(origin.author)
1959
2012
  ] })
1960
2013
  ] }) });
1961
2014
  }
@@ -1965,17 +2018,25 @@ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1965
2018
  function OriginInput(props) {
1966
2019
  const value = props.value;
1967
2020
  if (!value) return props.renderDefault(props);
2021
+ const str = (key) => typeof value[key] === "string" ? value[key] : void 0;
2022
+ const obj = (key) => value[key] !== null && typeof value[key] === "object" ? value[key] : void 0;
1968
2023
  const origin = {
1969
- repo: typeof value.repo === "string" ? value.repo : void 0,
1970
- repoOwner: typeof value.repoOwner === "string" ? value.repoOwner : void 0,
1971
- repoName: typeof value.repoName === "string" ? value.repoName : void 0,
1972
- path: typeof value.path === "string" ? value.path : void 0,
1973
- branch: typeof value.branch === "string" ? value.branch : void 0,
1974
- commitSha: typeof value.commitSha === "string" ? value.commitSha : void 0,
1975
- lastSyncedAt: typeof value.lastSyncedAt === "string" ? value.lastSyncedAt : void 0
2024
+ repo: str("repo"),
2025
+ repoOwner: str("repoOwner"),
2026
+ repoName: str("repoName"),
2027
+ path: str("path"),
2028
+ branch: str("branch"),
2029
+ commitSha: str("commitSha"),
2030
+ lastSyncedAt: str("lastSyncedAt"),
2031
+ author: obj("author"),
2032
+ lastEditor: obj("lastEditor"),
2033
+ graduatedAt: str("graduatedAt"),
2034
+ graduatedBy: obj("graduatedBy")
1976
2035
  };
2036
+ const ownershipValue = useFormValue(["ownership"]);
2037
+ const ownership = typeof ownershipValue === "string" ? ownershipValue : void 0;
1977
2038
  return /* @__PURE__ */ jsxs7(Stack5, { space: 3, children: [
1978
- /* @__PURE__ */ jsx7(MirrorBanner, { origin }),
2039
+ /* @__PURE__ */ jsx7(MirrorBanner, { origin, ownership }),
1979
2040
  props.renderDefault(props)
1980
2041
  ] });
1981
2042
  }
@@ -2147,6 +2208,57 @@ var taskSchema = defineType5({
2147
2208
  validation: (rule) => rule.required()
2148
2209
  }),
2149
2210
  // -----------------------------------------------------------------------
2211
+ // Ownership — who is the source of truth for this task
2212
+ //
2213
+ // "studio" tasks are fully editable in Studio. "repo" tasks are managed
2214
+ // in an external repository and read-only in Studio. Mirrored tasks are
2215
+ // set to "repo" by the pipeline; graduation changes this to "studio".
2216
+ //
2217
+ // Absent = "studio" for backwards compatibility with existing native tasks.
2218
+ // -----------------------------------------------------------------------
2219
+ defineField5({
2220
+ description: 'Who is the source of truth for this task. "studio" tasks are fully editable here. "repo" tasks are managed in an external repository and read-only in Studio.',
2221
+ group: ["optional", "all-fields"],
2222
+ hidden: true,
2223
+ // Managed by system actions, not manually edited
2224
+ initialValue: "studio",
2225
+ name: "ownership",
2226
+ options: {
2227
+ list: [
2228
+ { title: "Studio", value: "studio" },
2229
+ { title: "Repository", value: "repo" }
2230
+ ]
2231
+ },
2232
+ title: "Ownership",
2233
+ type: "string"
2234
+ }),
2235
+ // -----------------------------------------------------------------------
2236
+ // Status — task lifecycle state
2237
+ //
2238
+ // Active tasks run in evaluations and appear in default list views.
2239
+ // Exploratory tasks are excluded from production evals (for testing).
2240
+ // Archived tasks are retired, hidden from default views, and preserved
2241
+ // for historical report references.
2242
+ // -----------------------------------------------------------------------
2243
+ defineField5({
2244
+ description: "Task lifecycle status. Active tasks run in evaluations. Draft tasks are work-in-progress, excluded from production evals. Paused tasks are temporarily suspended. Archived tasks are retired and excluded from evaluations.",
2245
+ group: ["main", "all-fields"],
2246
+ initialValue: "active",
2247
+ name: "status",
2248
+ options: {
2249
+ direction: "horizontal",
2250
+ layout: "radio",
2251
+ list: [
2252
+ { title: "Active", value: "active" },
2253
+ { title: "Draft", value: "draft" },
2254
+ { title: "Paused", value: "paused" },
2255
+ { title: "Archived", value: "archived" }
2256
+ ]
2257
+ },
2258
+ title: "Status",
2259
+ type: "string"
2260
+ }),
2261
+ // -----------------------------------------------------------------------
2150
2262
  // Task prompt
2151
2263
  // -----------------------------------------------------------------------
2152
2264
  defineField5({
@@ -2679,6 +2791,95 @@ var taskSchema = defineType5({
2679
2791
  readOnly: true,
2680
2792
  title: "Last Synced At",
2681
2793
  type: "datetime"
2794
+ }),
2795
+ // --- Authorship tracking (Phase 4: Task Lifecycle) ---
2796
+ defineField5({
2797
+ description: "Who originally created this task in the source repo. Set on first mirror, never overwritten on subsequent syncs.",
2798
+ fields: [
2799
+ defineField5({
2800
+ name: "gitName",
2801
+ readOnly: true,
2802
+ title: "Git Name",
2803
+ type: "string"
2804
+ }),
2805
+ defineField5({
2806
+ name: "gitEmail",
2807
+ readOnly: true,
2808
+ title: "Git Email",
2809
+ type: "string"
2810
+ }),
2811
+ defineField5({
2812
+ name: "githubUsername",
2813
+ readOnly: true,
2814
+ title: "GitHub Username",
2815
+ type: "string"
2816
+ })
2817
+ ],
2818
+ name: "author",
2819
+ readOnly: true,
2820
+ title: "Original Author",
2821
+ type: "object"
2822
+ }),
2823
+ defineField5({
2824
+ description: "Who last modified this task in the source repo. Updated on every content-changing mirror sync.",
2825
+ fields: [
2826
+ defineField5({
2827
+ name: "gitName",
2828
+ readOnly: true,
2829
+ title: "Git Name",
2830
+ type: "string"
2831
+ }),
2832
+ defineField5({
2833
+ name: "gitEmail",
2834
+ readOnly: true,
2835
+ title: "Git Email",
2836
+ type: "string"
2837
+ }),
2838
+ defineField5({
2839
+ name: "githubUsername",
2840
+ readOnly: true,
2841
+ title: "GitHub Username",
2842
+ type: "string"
2843
+ })
2844
+ ],
2845
+ name: "lastEditor",
2846
+ readOnly: true,
2847
+ title: "Last Editor",
2848
+ type: "object"
2849
+ }),
2850
+ defineField5({
2851
+ description: "When this task was graduated to Studio ownership",
2852
+ name: "graduatedAt",
2853
+ readOnly: true,
2854
+ title: "Graduated At",
2855
+ type: "datetime"
2856
+ }),
2857
+ defineField5({
2858
+ description: "Who graduated this task from repo to Studio ownership",
2859
+ fields: [
2860
+ defineField5({
2861
+ name: "sanityId",
2862
+ readOnly: true,
2863
+ title: "Sanity User ID",
2864
+ type: "string"
2865
+ }),
2866
+ defineField5({
2867
+ name: "name",
2868
+ readOnly: true,
2869
+ title: "Name",
2870
+ type: "string"
2871
+ }),
2872
+ defineField5({
2873
+ name: "email",
2874
+ readOnly: true,
2875
+ title: "Email",
2876
+ type: "string"
2877
+ })
2878
+ ],
2879
+ name: "graduatedBy",
2880
+ readOnly: true,
2881
+ title: "Graduated By",
2882
+ type: "object"
2682
2883
  })
2683
2884
  ],
2684
2885
  components: {
@@ -2697,9 +2898,16 @@ var taskSchema = defineType5({
2697
2898
  ],
2698
2899
  name: "ailf.task",
2699
2900
  preview: {
2700
- prepare({ area, description, id, origin }) {
2901
+ prepare({
2902
+ area,
2903
+ description,
2904
+ id,
2905
+ origin,
2906
+ ownership,
2907
+ status
2908
+ }) {
2701
2909
  const taskId = id !== null && typeof id === "object" && "current" in id ? id.current : void 0;
2702
- const isMirror = origin !== null && typeof origin === "object" && "repo" in origin;
2910
+ const isMirror = ownership === "repo" || !ownership && origin !== null && typeof origin === "object" && "repo" in origin;
2703
2911
  const areaId = area !== null && typeof area === "object" && "current" in area ? area.current : void 0;
2704
2912
  const prefix = isMirror ? "\u{1F517} " : "";
2705
2913
  const areaStr = typeof areaId === "string" ? `[${areaId}] ` : "";
@@ -2717,22 +2925,32 @@ var taskSchema = defineType5({
2717
2925
  syncInfo = ` \xB7 ${icon} ${ageLabel}`;
2718
2926
  }
2719
2927
  }
2928
+ const statusIcon = status === "archived" ? "\u{1F4E6} " : status === "draft" ? "\u{1F9EA} " : status === "paused" ? "\u23F8\uFE0F " : "";
2720
2929
  return {
2721
2930
  subtitle: `${areaStr}${typeof taskId === "string" ? taskId : ""}${syncInfo}`,
2722
- title: `${prefix}${typeof description === "string" ? description : "Task"}`
2931
+ title: `${prefix}${statusIcon}${typeof description === "string" ? description : "Task"}`
2723
2932
  };
2724
2933
  },
2725
2934
  select: {
2726
2935
  area: "featureArea.areaId",
2727
2936
  description: "description",
2728
2937
  id: "id",
2729
- origin: "origin"
2938
+ origin: "origin",
2939
+ ownership: "ownership",
2940
+ status: "status"
2730
2941
  }
2731
2942
  },
2732
- // Document-level read-only when mirrored from a repo.
2733
- // Native tasks (no origin) are fully editable; mirrored tasks
2734
- // are read-only because the source of truth is the repo.
2735
- readOnly: ({ document: document2 }) => !!document2?.origin,
2943
+ // Document-level read-only when owned by a repo.
2944
+ // Native tasks (ownership: "studio" or absent) are fully editable;
2945
+ // repo-owned tasks are read-only because the source of truth is the repo.
2946
+ // Falls back to checking origin for legacy mirrored tasks that don't
2947
+ // have the ownership field yet (pre-migration).
2948
+ readOnly: ({ document: document2 }) => {
2949
+ const doc = document2;
2950
+ if (doc?.ownership === "repo") return true;
2951
+ if (doc?.ownership === "studio" || doc?.ownership) return false;
2952
+ return !!doc?.origin;
2953
+ },
2736
2954
  title: "AILF Task",
2737
2955
  type: "document"
2738
2956
  });
@@ -3272,7 +3490,7 @@ function LoadingState({ message = "Loading\u2026" }) {
3272
3490
  }
3273
3491
 
3274
3492
  // src/components/ComparisonView.tsx
3275
- import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
3493
+ import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
3276
3494
  function ComparisonView() {
3277
3495
  const client = useClient3({ apiVersion: API_VERSION });
3278
3496
  const [baselineId, setBaselineId] = useState3(null);
@@ -3370,7 +3588,7 @@ function ComparisonView() {
3370
3588
  ] }),
3371
3589
  hasBoth && loading && /* @__PURE__ */ jsx12(LoadingState, { message: "Loading comparison\u2026" }),
3372
3590
  hasBoth && !loading && !hasData && /* @__PURE__ */ jsx12(Card6, { padding: 4, tone: "caution", children: /* @__PURE__ */ jsx12(Text11, { size: 2, children: "Report not found." }) }),
3373
- hasBoth && !loading && hasData && /* @__PURE__ */ jsxs10(Fragment3, { children: [
3591
+ hasBoth && !loading && hasData && /* @__PURE__ */ jsxs10(Fragment4, { children: [
3374
3592
  /* @__PURE__ */ jsx12(
3375
3593
  AreaComparisonTable,
3376
3594
  {
@@ -3817,7 +4035,7 @@ function formatCardDate(iso) {
3817
4035
  }
3818
4036
 
3819
4037
  // src/components/report-table/ReportTable.tsx
3820
- import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
4038
+ import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
3821
4039
  function GitBranchIcon({ style }) {
3822
4040
  return /* @__PURE__ */ jsxs12(
3823
4041
  "svg",
@@ -3862,7 +4080,7 @@ function ReportTable({
3862
4080
  onSortChange
3863
4081
  }) {
3864
4082
  const { ref, tier } = useContainerWidth();
3865
- return /* @__PURE__ */ jsxs12(Fragment4, { children: [
4083
+ return /* @__PURE__ */ jsxs12(Fragment5, { children: [
3866
4084
  /* @__PURE__ */ jsx15("style", { children: TABLE_HOVER_STYLES }),
3867
4085
  /* @__PURE__ */ jsxs12("div", { ref, children: [
3868
4086
  /* @__PURE__ */ jsxs12(
@@ -3895,7 +4113,7 @@ function ReportTable({
3895
4113
  onClick: () => onSortChange("score")
3896
4114
  }
3897
4115
  ),
3898
- tier === "full" && /* @__PURE__ */ jsxs12(Fragment4, { children: [
4116
+ tier === "full" && /* @__PURE__ */ jsxs12(Fragment5, { children: [
3899
4117
  /* @__PURE__ */ jsx15(ColHeader, { label: "Mode" }),
3900
4118
  /* @__PURE__ */ jsx15(ColHeader, { label: "Trigger" }),
3901
4119
  /* @__PURE__ */ jsx15(
@@ -4423,7 +4641,7 @@ import {
4423
4641
  // src/components/primitives/StatCard.tsx
4424
4642
  import { HelpCircleIcon as HelpCircleIcon4 } from "@sanity/icons";
4425
4643
  import { Box as Box8, Card as Card8, Stack as Stack11, Text as Text15, Tooltip as Tooltip3 } from "@sanity/ui";
4426
- import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
4644
+ import { Fragment as Fragment6, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
4427
4645
  function StatCard({
4428
4646
  label,
4429
4647
  value,
@@ -4434,7 +4652,7 @@ function StatCard({
4434
4652
  return /* @__PURE__ */ jsx17(Card8, { padding: 3, radius: 2, shadow: 1, tone, children: /* @__PURE__ */ jsxs14(Stack11, { space: 2, children: [
4435
4653
  /* @__PURE__ */ jsxs14(Text15, { muted: true, size: 2, children: [
4436
4654
  label,
4437
- tooltip && /* @__PURE__ */ jsxs14(Fragment5, { children: [
4655
+ tooltip && /* @__PURE__ */ jsxs14(Fragment6, { children: [
4438
4656
  " ",
4439
4657
  /* @__PURE__ */ jsx17(
4440
4658
  Tooltip3,
@@ -5106,10 +5324,10 @@ import { HelpCircleIcon as HelpCircleIcon6 } from "@sanity/icons";
5106
5324
  import { Badge as Badge8, Box as Box11, Card as Card14, Flex as Flex14, Stack as Stack16, Text as Text21, Tooltip as Tooltip6 } from "@sanity/ui";
5107
5325
 
5108
5326
  // src/components/primitives/InlineCode.tsx
5109
- import { Fragment as Fragment6, jsx as jsx23 } from "react/jsx-runtime";
5327
+ import { Fragment as Fragment7, jsx as jsx23 } from "react/jsx-runtime";
5110
5328
  function InlineCode({ text }) {
5111
5329
  const parts = text.split(/`([^`]+)`/);
5112
- return /* @__PURE__ */ jsx23(Fragment6, { children: parts.map(
5330
+ return /* @__PURE__ */ jsx23(Fragment7, { children: parts.map(
5113
5331
  (part, i) => i % 2 === 1 ? /* @__PURE__ */ jsx23(
5114
5332
  "code",
5115
5333
  {
@@ -5882,7 +6100,7 @@ function DownloadReportAction({
5882
6100
  import { PlayIcon as PlayIcon2 } from "@sanity/icons";
5883
6101
  import { MenuItem as MenuItem6, useToast as useToast6 } from "@sanity/ui";
5884
6102
  import { useCallback as useCallback16, useState as useState11 } from "react";
5885
- import { useClient as useClient8, useCurrentUser as useCurrentUser2 } from "sanity";
6103
+ import { useClient as useClient8, useCurrentUser as useCurrentUser3 } from "sanity";
5886
6104
 
5887
6105
  // src/lib/eval-scope.ts
5888
6106
  function extractEvalScope(provenance) {
@@ -5934,7 +6152,7 @@ function RerunEvaluationAction({
5934
6152
  reportId
5935
6153
  }) {
5936
6154
  const client = useClient8({ apiVersion: API_VERSION });
5937
- const currentUser = useCurrentUser2();
6155
+ const currentUser = useCurrentUser3();
5938
6156
  const toast = useToast6();
5939
6157
  const [requesting, setRequesting] = useState11(false);
5940
6158
  const handleClick = useCallback16(async () => {
@@ -5976,7 +6194,7 @@ function RerunEvaluationAction({
5976
6194
  }
5977
6195
 
5978
6196
  // src/components/report-detail/report-actions/ReportActions.tsx
5979
- import { Fragment as Fragment7, jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
6197
+ import { Fragment as Fragment8, jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
5980
6198
  function ReportActions({
5981
6199
  documentId,
5982
6200
  onDeleted,
@@ -6032,7 +6250,7 @@ function ReportActions({
6032
6250
  setDeleting(false);
6033
6251
  }
6034
6252
  }, [client, documentId, onDeleted, toast]);
6035
- return /* @__PURE__ */ jsxs24(Fragment7, { children: [
6253
+ return /* @__PURE__ */ jsxs24(Fragment8, { children: [
6036
6254
  /* @__PURE__ */ jsxs24(Flex18, { children: [
6037
6255
  /* @__PURE__ */ jsx35(
6038
6256
  Button3,
@@ -6812,7 +7030,7 @@ import { useCallback as useCallback21, useEffect as useEffect9, useRef as useRef
6812
7030
  import {
6813
7031
  getReleaseIdFromReleaseDocumentId as getReleaseIdFromReleaseDocumentId3,
6814
7032
  useClient as useClient12,
6815
- useCurrentUser as useCurrentUser3,
7033
+ useCurrentUser as useCurrentUser4,
6816
7034
  useDataset as useDataset2,
6817
7035
  useProjectId as useProjectId2
6818
7036
  } from "sanity";
@@ -6837,7 +7055,7 @@ function createRunEvaluationAction(options = {}) {
6837
7055
  const client = useClient12({ apiVersion: API_VERSION2 });
6838
7056
  const dataset = useDataset2();
6839
7057
  const projectId = useProjectId2();
6840
- const currentUser = useCurrentUser3();
7058
+ const currentUser = useCurrentUser4();
6841
7059
  const toast = useToast8();
6842
7060
  const [state, setState] = useState15({ status: "loading" });
6843
7061
  const requestedAtRef = useRef4(null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/ailf-studio",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "AI Literacy Framework — Sanity Studio dashboard plugin",
5
5
  "type": "module",
6
6
  "license": "MIT",