bsmnt 0.0.0

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 (98) hide show
  1. package/.changeset/2026-02-11-test-patch-bump.md +5 -0
  2. package/.changeset/README.md +10 -0
  3. package/.changeset/config.json +16 -0
  4. package/.cursor/rules/README.md +184 -0
  5. package/.cursor/rules/architecture.mdc +437 -0
  6. package/.cursor/rules/components.mdc +436 -0
  7. package/.cursor/rules/integrations.mdc +447 -0
  8. package/.cursor/rules/main.mdc +278 -0
  9. package/.cursor/rules/styling.mdc +433 -0
  10. package/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  11. package/.github/workflows/.gitkeep +0 -0
  12. package/.github/workflows/ci.yml +37 -0
  13. package/.github/workflows/release.yml +54 -0
  14. package/.tldr/cache/call_graph.json +7 -0
  15. package/.tldr/languages.json +6 -0
  16. package/.tldr/status +1 -0
  17. package/.tldrignore +84 -0
  18. package/.vscode/extensions.json +20 -0
  19. package/.vscode/settings.json +98 -0
  20. package/CHANGELOG.md +13 -0
  21. package/CLAUDE.md +138 -0
  22. package/README.md +176 -0
  23. package/bin/index.js +262 -0
  24. package/biome.json +44 -0
  25. package/bun.lock +496 -0
  26. package/changelog/04-02-26.md +86 -0
  27. package/changelog/05-02-26.md +101 -0
  28. package/changelog/09-02-26.md +83 -0
  29. package/docs/fix-studio-hydration.md +46 -0
  30. package/docs/plans/2026-01-29-sanity-smart-merge-design.md +196 -0
  31. package/docs/plans/2026-01-29-sanity-smart-merge-implementation.md +695 -0
  32. package/docs/sanity-setup-steps.md +199 -0
  33. package/integrations/basehub/README.md +3 -0
  34. package/integrations/sanity/app/api/draft-mode/disable/route.ts +7 -0
  35. package/integrations/sanity/app/api/draft-mode/enable/route.ts +21 -0
  36. package/integrations/sanity/app/api/revalidate/route.ts +37 -0
  37. package/integrations/sanity/app/layout.tsx +111 -0
  38. package/integrations/sanity/app/sitemap.ts +80 -0
  39. package/integrations/sanity/app/studio/[[...tool]]/page.tsx +8 -0
  40. package/integrations/sanity/app/studio/layout.tsx +7 -0
  41. package/integrations/sanity/components/ui/sanity-image/index.tsx +37 -0
  42. package/integrations/sanity/lib/integrations/README.md +58 -0
  43. package/integrations/sanity/lib/integrations/check-integration.ts +62 -0
  44. package/integrations/sanity/lib/integrations/sanity/README.md +144 -0
  45. package/integrations/sanity/lib/integrations/sanity/client.ts +30 -0
  46. package/integrations/sanity/lib/integrations/sanity/components/disable-draft-mode.tsx +29 -0
  47. package/integrations/sanity/lib/integrations/sanity/components/rich-text.tsx +73 -0
  48. package/integrations/sanity/lib/integrations/sanity/env.ts +38 -0
  49. package/integrations/sanity/lib/integrations/sanity/live/index.tsx +34 -0
  50. package/integrations/sanity/lib/integrations/sanity/queries.ts +99 -0
  51. package/integrations/sanity/lib/integrations/sanity/sanity.cli.ts +20 -0
  52. package/integrations/sanity/lib/integrations/sanity/sanity.config.ts +94 -0
  53. package/integrations/sanity/lib/integrations/sanity/sanity.types.ts +337 -0
  54. package/integrations/sanity/lib/integrations/sanity/schema.json +1850 -0
  55. package/integrations/sanity/lib/integrations/sanity/schemas/article.ts +132 -0
  56. package/integrations/sanity/lib/integrations/sanity/schemas/example.ts +203 -0
  57. package/integrations/sanity/lib/integrations/sanity/schemas/index.ts +37 -0
  58. package/integrations/sanity/lib/integrations/sanity/schemas/link.ts +127 -0
  59. package/integrations/sanity/lib/integrations/sanity/schemas/metadata.ts +68 -0
  60. package/integrations/sanity/lib/integrations/sanity/schemas/navigation.ts +39 -0
  61. package/integrations/sanity/lib/integrations/sanity/schemas/page.ts +77 -0
  62. package/integrations/sanity/lib/integrations/sanity/schemas/richText.ts +59 -0
  63. package/integrations/sanity/lib/integrations/sanity/structure.ts +5 -0
  64. package/integrations/sanity/lib/integrations/sanity/utils/image.ts +11 -0
  65. package/integrations/sanity/lib/integrations/sanity/utils/link.ts +61 -0
  66. package/integrations/sanity/lib/scripts/copy-sanity-mcp.ts +23 -0
  67. package/integrations/sanity/lib/scripts/generate-page.ts +310 -0
  68. package/integrations/sanity/lib/utils/metadata.ts +190 -0
  69. package/layers/experiment/components/layout/header/index.tsx +58 -0
  70. package/layers/experiment/components/layout/navigation-menu.tsx +127 -0
  71. package/layers/experiment/lib/constants.ts +12 -0
  72. package/layers/webgl/app/page.tsx +10 -0
  73. package/layers/webgl/components/webgl/canvas/dynamic.tsx +34 -0
  74. package/layers/webgl/components/webgl/canvas/index.tsx +43 -0
  75. package/layers/webgl/components/webgl/components/scene/index.tsx +21 -0
  76. package/layers/webgpu/.gitkeep +0 -0
  77. package/package.json +44 -0
  78. package/plugins/README.md +21 -0
  79. package/plugins/no-anchor-element.grit +11 -0
  80. package/plugins/no-relative-parent-imports.grit +6 -0
  81. package/plugins/no-unnecessary-forwardref.grit +5 -0
  82. package/src/commands/add-integration.js +325 -0
  83. package/src/commands/create.js +415 -0
  84. package/src/commands/setup-sanity.js +426 -0
  85. package/src/commands/worktree.js +805 -0
  86. package/src/mergers/check-integration-merger.js +105 -0
  87. package/src/mergers/config.js +137 -0
  88. package/src/mergers/index.js +355 -0
  89. package/src/mergers/layout-merger.js +223 -0
  90. package/src/mergers/next-config-merger.js +63 -0
  91. package/src/mergers/sitemap-merger.js +121 -0
  92. package/tasks/prd-next-starter-dynamic-layers.md +184 -0
  93. package/tasks/prd.json +153 -0
  94. package/tasks/progress.txt +115 -0
  95. package/template-hooks/use-battery.ts +126 -0
  96. package/template-hooks/use-device-perf.ts +184 -0
  97. package/template-hooks/use-intersection-observer.ts +32 -0
  98. package/template-hooks/use-media.ts +33 -0
@@ -0,0 +1,144 @@
1
+ # Sanity CMS Integration
2
+
3
+ Headless CMS with visual editing.
4
+
5
+ ## Environment Variables
6
+
7
+ ```env
8
+ # Required
9
+ NEXT_PUBLIC_SANITY_PROJECT_ID="your-project-id"
10
+ NEXT_PUBLIC_SANITY_DATASET="production"
11
+
12
+ # Required for Visual Editing & Live Preview
13
+ NEXT_PUBLIC_SANITY_API_READ_TOKEN="your-viewer-token"
14
+ SANITY_PRIVATE_TOKEN="your-editor-token"
15
+
16
+ # Optional
17
+ NEXT_PUBLIC_SANITY_STUDIO_URL="http://localhost:3000/studio"
18
+ NEXT_PUBLIC_SANITY_API_VERSION="2024-03-15"
19
+ ```
20
+
21
+ > **Note**: Create tokens in [Sanity Dashboard](https://sanity.io/manage) → Your Project → API → Tokens.
22
+ > - **Viewer** token → `NEXT_PUBLIC_SANITY_API_READ_TOKEN`
23
+ > - **Editor** token → `SANITY_PRIVATE_TOKEN`
24
+
25
+ ## Quick Start
26
+
27
+ 1. Access Studio at `/studio`
28
+ 2. Create content (Pages, Articles)
29
+ 3. Click "Present" for visual editing
30
+
31
+ ## Usage
32
+
33
+ ### Fetching Data
34
+
35
+ ```tsx
36
+ import { sanityFetch } from 'next-sanity/live'
37
+ import { pageQuery } from '@/lib/integrations/sanity/queries'
38
+
39
+ export default async function Page({ params }) {
40
+ const { data } = await sanityFetch({
41
+ query: pageQuery,
42
+ params: { slug: params.slug }
43
+ })
44
+ return <YourComponent data={data} />
45
+ }
46
+ ```
47
+
48
+ ### Build-time Data Fetching
49
+
50
+ For `generateStaticParams` or other build-time functions, use the client directly:
51
+
52
+ ```tsx
53
+ import { client } from '@/lib/integrations/sanity/client'
54
+ import { allArticlesQuery } from '@/lib/integrations/sanity/queries'
55
+
56
+ export async function generateStaticParams() {
57
+ if (!client) return []
58
+ const data = await client.fetch(allArticlesQuery)
59
+ return data.map((item) => ({ slug: item.slug?.current ?? '' }))
60
+ }
61
+ ```
62
+
63
+ ### Visual Editing
64
+
65
+ Add `data-sanity` attributes for visual editing:
66
+
67
+ ```tsx
68
+ import { RichText } from '@/lib/integrations/sanity/components/rich-text'
69
+
70
+ function MyComponent({ data }) {
71
+ return (
72
+ <div data-sanity={data._id}>
73
+ <h1 data-sanity="title">{data.title}</h1>
74
+ <RichText content={data.content} />
75
+ </div>
76
+ )
77
+ }
78
+ ```
79
+
80
+ ### Image Handling
81
+
82
+ ```tsx
83
+ import { urlForImage } from '@/lib/integrations/sanity/utils/image'
84
+ import { SanityImage } from '@/components/ui/sanity-image'
85
+
86
+ // Option 1: Using urlForImage utility
87
+ <img src={urlForImage(image).width(800).url()} alt={image.alt} />
88
+
89
+ // Option 2: Using SanityImage component
90
+ <SanityImage image={image} maxWidth={800} />
91
+ ```
92
+
93
+ ### SEO Metadata
94
+
95
+ ```tsx
96
+ import { generateSanityMetadata } from '@/lib/utils/metadata'
97
+
98
+ export async function generateMetadata({ params }) {
99
+ const { data } = await sanityFetch({ query: pageQuery, params })
100
+ return generateSanityMetadata({ document: data, url: `/page/${params.slug}` })
101
+ }
102
+ ```
103
+
104
+ ## Creating New Content Types
105
+
106
+ 1. **Create schema** in `schemaTypes/`:
107
+
108
+ ```typescript
109
+ import { defineField, defineType } from 'sanity'
110
+
111
+ export const landing = defineType({
112
+ name: 'landing',
113
+ type: 'document',
114
+ fields: [
115
+ defineField({ name: 'title', type: 'string' }),
116
+ defineField({ name: 'slug', type: 'slug', options: { source: 'title' } }),
117
+ defineField({ name: 'content', type: 'richText' }),
118
+ ],
119
+ })
120
+ ```
121
+
122
+ 2. **Add to schema index** in `schemaTypes/index.ts`
123
+ 3. **Create query** in `queries.ts`
124
+ 4. **Create page** in `app/`
125
+
126
+ ## Caching
127
+
128
+ - **Draft mode**: Uses `cache: 'no-store'` automatically
129
+ - **Published content**: ISR with `revalidate: 3600`
130
+ - All queries use `cacheSignal()` for automatic cleanup
131
+
132
+ See [ARCHITECTURE.md](../../../ARCHITECTURE.md) for cache gotchas.
133
+
134
+ ## Troubleshooting
135
+
136
+ **Visual editor not loading:**
137
+ - Check env vars are set correctly
138
+ - Verify draft mode routes exist (`/api/draft-mode/enable`)
139
+
140
+ **Content not updating:**
141
+ - Hard refresh browser
142
+ - Check revalidation webhook is configured
143
+
144
+ **Related**: [Sanity Docs](https://www.sanity.io/docs) · [Parent README](../README.md)
@@ -0,0 +1,30 @@
1
+ import { createClient, type SanityClient } from "next-sanity";
2
+ import { isSanityConfigured } from "@/lib/integrations/check-integration";
3
+ import { apiVersion, dataset, privateToken, projectId, studioUrl } from "./env";
4
+
5
+ /**
6
+ * Sanity client instance
7
+ *
8
+ * Returns null if Sanity is not configured (missing env vars).
9
+ * Always check with isSanityConfigured() before using.
10
+ */
11
+ export const client: SanityClient | null = isSanityConfigured()
12
+ ? createClient({
13
+ projectId,
14
+ dataset,
15
+ apiVersion,
16
+ useCdn: true,
17
+ perspective: "published",
18
+ token: privateToken,
19
+ stega: {
20
+ studioUrl,
21
+ filter: (props) => {
22
+ if (props.sourcePath.at(-1) === "title") {
23
+ return true;
24
+ }
25
+
26
+ return props.filterDefault(props);
27
+ },
28
+ },
29
+ })
30
+ : null;
@@ -0,0 +1,29 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { usePathname } from "next/navigation";
5
+ import { useDraftModeEnvironment } from "next-sanity/hooks";
6
+
7
+ export function DisableDraftMode() {
8
+ const environment = useDraftModeEnvironment();
9
+ const pathname = usePathname();
10
+
11
+ // Only show the disable draft mode button when outside of Presentation Tool
12
+ if (environment !== "live" && environment !== "unknown") {
13
+ return null;
14
+ }
15
+
16
+ if (pathname.startsWith("/studio")) {
17
+ return null;
18
+ }
19
+
20
+ return (
21
+ <Link
22
+ href="/api/draft-mode/disable"
23
+ scroll={false}
24
+ className="dr-p-4 fixed top-safe right-safe z-50 bg-red font-mono text-primary text-sm uppercase"
25
+ >
26
+ Disable Draft Mode
27
+ </Link>
28
+ );
29
+ }
@@ -0,0 +1,73 @@
1
+ import { PortableText, type PortableTextBlock } from "@portabletext/react";
2
+ import { Link } from "@/components/ui/link";
3
+ import { SanityImage } from "@/components/ui/sanity-image";
4
+
5
+ interface RichTextProps {
6
+ content: PortableTextBlock[];
7
+ }
8
+
9
+ interface LinkFieldData {
10
+ type: "external" | "internal";
11
+ url?: string;
12
+ blank?: boolean;
13
+ label?: string;
14
+ }
15
+
16
+ function resolveLinkFieldUrl(linkData: LinkFieldData): string {
17
+ if (!linkData) return "#";
18
+
19
+ // External URL
20
+ if (linkData.type === "external" && linkData.url) {
21
+ return linkData.url;
22
+ }
23
+
24
+ // Internal path (like /home, /hubspot, /r3f, etc.)
25
+ if (linkData.type === "internal" && linkData.url) {
26
+ return linkData.url;
27
+ }
28
+
29
+ return "#";
30
+ }
31
+
32
+ export function RichText({ content }: RichTextProps) {
33
+ if (!content) return null;
34
+
35
+ return (
36
+ <PortableText
37
+ value={content}
38
+ components={{
39
+ types: {
40
+ image: ({ value }) => <SanityImage image={value} maxWidth={1920} />,
41
+ },
42
+ marks: {
43
+ link: ({ children, value }) => {
44
+ const href = resolveLinkFieldUrl(value);
45
+ const isExternal = value?.type === "url";
46
+
47
+ return (
48
+ <Link
49
+ href={href}
50
+ target={isExternal && value?.blank ? "_blank" : undefined}
51
+ rel={
52
+ isExternal && value?.blank ? "noopener noreferrer" : undefined
53
+ }
54
+ data-sanity-edit-target
55
+ >
56
+ {children}
57
+ </Link>
58
+ );
59
+ },
60
+ },
61
+ block: {
62
+ h1: ({ children }) => <h1 className="h1">{children}</h1>,
63
+ h2: ({ children }) => <h2 className="h2">{children}</h2>,
64
+ h3: ({ children }) => <h3 className="h3">{children}</h3>,
65
+ h4: ({ children }) => <h4 className="h4">{children}</h4>,
66
+ h5: ({ children }) => <h5 className="h5">{children}</h5>,
67
+ h6: ({ children }) => <h6 className="h6">{children}</h6>,
68
+ normal: ({ children }) => <p className="p">{children}</p>,
69
+ },
70
+ }}
71
+ />
72
+ );
73
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Sanity Environment Configuration
3
+ *
4
+ * All values have safe defaults to allow the app to build and run
5
+ * without Sanity configured. Use isSanityConfigured() to check
6
+ * if Sanity is properly set up before making API calls.
7
+ */
8
+
9
+ /** Sanity API version - defaults to latest stable */
10
+ export const apiVersion =
11
+ process.env.NEXT_PUBLIC_SANITY_API_VERSION || "2024-03-15";
12
+
13
+ /** Sanity dataset name - defaults to 'production' */
14
+ export const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET || "production";
15
+
16
+ /** Sanity project ID - empty string if not configured */
17
+ export const projectId =
18
+ process.env.NEXT_PUBLIC_SANITY_PROJECT_ID ||
19
+ process.env.SANITY_STUDIO_PROJECT_ID ||
20
+ "";
21
+
22
+ /** Sanity Studio URL for visual editing */
23
+ export const studioUrl =
24
+ process.env.NODE_ENV === "development"
25
+ ? "http://localhost:3000/studio"
26
+ : `${process.env.NEXT_PUBLIC_BASE_URL || ""}/studio`;
27
+
28
+ /** Public read token for client-side queries */
29
+ export const publicToken = process.env.NEXT_PUBLIC_SANITY_API_READ_TOKEN || "";
30
+
31
+ /** Private token for server-side mutations (optional) */
32
+ export const privateToken = process.env.SANITY_PRIVATE_TOKEN || "";
33
+
34
+ /** Preview URL for draft mode */
35
+ export const previewURL =
36
+ process.env.NODE_ENV === "development"
37
+ ? "http://localhost:3000"
38
+ : process.env.NEXT_PUBLIC_BASE_URL || "";
@@ -0,0 +1,34 @@
1
+ import { defineLive } from "next-sanity/live";
2
+ import { isSanityConfigured } from "@/lib/integrations/check-integration";
3
+ import { client } from "../client";
4
+ import { privateToken, publicToken } from "../env";
5
+
6
+ /**
7
+ * Sanity Live configuration
8
+ *
9
+ * When Sanity is not configured, provides stub implementation
10
+ * that returns null instead of throwing errors.
11
+ */
12
+ const isConfigured = isSanityConfigured() && client;
13
+
14
+ const liveExports = isConfigured
15
+ ? defineLive({
16
+ // biome-ignore lint/style/noNonNullAssertion: client is checked via isConfigured above
17
+ client: client!,
18
+ browserToken: publicToken,
19
+ serverToken: privateToken,
20
+ })
21
+ : null;
22
+
23
+ /**
24
+ * Standard sanityFetch function from next-sanity/live.
25
+ * When Sanity is not configured, returns null data.
26
+ */
27
+ export const sanityFetch =
28
+ liveExports?.sanityFetch ?? (async () => ({ data: null }));
29
+
30
+ /**
31
+ * Sanity Live component for real-time updates.
32
+ * Returns null when Sanity is not configured.
33
+ */
34
+ export const SanityLive = liveExports?.SanityLive ?? (() => null);
@@ -0,0 +1,99 @@
1
+ import { groq } from "next-sanity";
2
+
3
+ // Helper for rich text content with link projections
4
+ const richTextWithLinks = `
5
+ content[]{
6
+ ...,
7
+ markDefs[]{
8
+ ...,
9
+ _type == "link" => {
10
+ ...,
11
+ internalLink->{_type, slug, title}
12
+ }
13
+ }
14
+ }
15
+ `;
16
+
17
+ const linkWithLabel = `
18
+ link {
19
+ ...,
20
+ internalLink->{_type, slug, title}
21
+ }
22
+ `;
23
+
24
+ // Page queries
25
+ export const pageQuery = groq`
26
+ *[_type == "page" && slug.current == $slug][0] {
27
+ _id,
28
+ title,
29
+ slug,
30
+ ${richTextWithLinks},
31
+ ${linkWithLabel},
32
+ metadata,
33
+ publishedAt,
34
+ _updatedAt
35
+ }
36
+ `;
37
+
38
+ export const pageByIdQuery = groq`
39
+ *[_type == "page" && _id == $id][0] {
40
+ _id,
41
+ title,
42
+ slug,
43
+ ${richTextWithLinks},
44
+ metadata,
45
+ publishedAt,
46
+ _updatedAt
47
+ }
48
+ `;
49
+
50
+ // Article queries
51
+ export const articleQuery = groq`
52
+ *[_type == "article" && slug.current == $slug][0] {
53
+ _id,
54
+ title,
55
+ slug,
56
+ excerpt,
57
+ featuredImage,
58
+ ${richTextWithLinks},
59
+ categories,
60
+ tags,
61
+ author,
62
+ publishedAt,
63
+ metadata,
64
+ _updatedAt
65
+ }
66
+ `;
67
+
68
+ export const allArticlesQuery = groq`
69
+ *[_type == "article"] | order(publishedAt desc) {
70
+ _id,
71
+ title,
72
+ slug,
73
+ excerpt,
74
+ featuredImage,
75
+ categories,
76
+ tags,
77
+ author,
78
+ publishedAt,
79
+ metadata,
80
+ _updatedAt
81
+ }
82
+ `;
83
+
84
+ export const articleByIdQuery = groq`
85
+ *[_type == "article" && _id == $id][0] {
86
+ _id,
87
+ title,
88
+ slug,
89
+ excerpt,
90
+ featuredImage,
91
+ ${richTextWithLinks},
92
+ categories,
93
+ tags,
94
+ author,
95
+ publishedAt,
96
+ metadata,
97
+ _updatedAt
98
+ }
99
+ `;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * This configuration file lets you run `$ sanity [command]` in this folder
3
+ * Go to https://www.sanity.io/docs/cli to learn more.
4
+ **/
5
+ import { defineCliConfig } from "sanity/cli";
6
+ import { dataset, projectId } from "./env";
7
+
8
+ export default defineCliConfig({
9
+ api: { projectId, dataset },
10
+ project: {
11
+ basePath: "/studio",
12
+ },
13
+ vite: {
14
+ resolve: {
15
+ alias: {
16
+ "~": __dirname,
17
+ },
18
+ },
19
+ },
20
+ });
@@ -0,0 +1,94 @@
1
+ /**
2
+ * This configuration is used to for the Sanity Studio that's mounted on the `/app/studio/[[...tool]]/page.tsx` route
3
+ */
4
+
5
+ import { visionTool } from "@sanity/vision";
6
+ import { defineConfig } from "sanity";
7
+ import {
8
+ defineDocuments,
9
+ defineLocations,
10
+ presentationTool,
11
+ } from "sanity/presentation";
12
+ import { structureTool } from "sanity/structure";
13
+ import { apiVersion, dataset, previewURL, projectId } from "./env";
14
+ import { schema } from "./schemas";
15
+ import { structure } from "./structure";
16
+
17
+ // Helper function for URL resolution
18
+ function resolveHref(documentType?: string, slug?: string): string | undefined {
19
+ switch (documentType) {
20
+ case "page":
21
+ return slug === "sanity" ? "/sanity" : `/sanity/${slug}`;
22
+ case "article":
23
+ return slug ? `/sanity/${slug}` : undefined;
24
+ default:
25
+ console.warn("Invalid document type:", documentType);
26
+ return undefined;
27
+ }
28
+ }
29
+
30
+ export default defineConfig({
31
+ basePath: "/studio",
32
+ projectId,
33
+ dataset,
34
+ schema,
35
+ plugins: [
36
+ // Presentation tool for visual editing
37
+ presentationTool({
38
+ resolve: {
39
+ // Map routes to documents and GROQ filters
40
+ mainDocuments: defineDocuments([
41
+ {
42
+ route: "/sanity",
43
+ filter: `_type == "page" && slug.current == "sanity"`,
44
+ },
45
+ {
46
+ route: "/sanity/:slug",
47
+ filter: `_type == "article" && slug.current == $slug && defined(slug.current)`,
48
+ },
49
+ ]),
50
+ locations: {
51
+ page: defineLocations({
52
+ select: {
53
+ title: "title",
54
+ slug: "slug.current",
55
+ },
56
+ resolve: (doc) => ({
57
+ locations: [
58
+ {
59
+ title: doc?.title || "Untitled Page",
60
+ href: resolveHref("page", doc?.slug) ?? "",
61
+ },
62
+ ],
63
+ }),
64
+ }),
65
+ article: defineLocations({
66
+ select: {
67
+ title: "title",
68
+ slug: "slug.current",
69
+ },
70
+ resolve: (doc) => ({
71
+ locations: [
72
+ {
73
+ title: doc?.title || "Untitled Article",
74
+ href: resolveHref("article", doc?.slug) ?? "",
75
+ },
76
+ ],
77
+ }),
78
+ }),
79
+ },
80
+ },
81
+ previewUrl: {
82
+ origin: previewURL,
83
+ draftMode: {
84
+ enable: "/api/draft-mode/enable",
85
+ disable: "/api/draft-mode/disable",
86
+ },
87
+ },
88
+ }),
89
+ structureTool({ structure }),
90
+ // Vision is for querying with GROQ from inside the Studio
91
+ // https://www.sanity.io/docs/the-vision-plugin
92
+ visionTool({ defaultApiVersion: apiVersion }),
93
+ ],
94
+ });