<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sunjay</title>
    <description>The latest articles on DEV Community by Sunjay (@beantownman).</description>
    <link>https://dev.to/beantownman</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2768953%2Fb48635db-ab4a-4fc9-a329-7e4a1262de2d.png</url>
      <title>DEV Community: Sunjay</title>
      <link>https://dev.to/beantownman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/beantownman"/>
    <language>en</language>
    <item>
      <title>PIM Lite on Sanity</title>
      <dc:creator>Sunjay</dc:creator>
      <pubDate>Sun, 04 Oct 2026 10:28:50 +0000</pubDate>
      <link>https://dev.to/beantownman/pim-lite-on-sanity-2b81</link>
      <guid>https://dev.to/beantownman/pim-lite-on-sanity-2b81</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/sanity-2026-09-16"&gt;Sanity Challenge, Path Two: Vibe-Code Something Strange&lt;/a&gt; The Architecture and some parts of text is my Coding agent generated based on what the Agent coded&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;h3&gt;
  
  
  PIM-Lite
&lt;/h3&gt;

&lt;p&gt;PIM usually means Product Information Management. In our case, PIM also means Product Intelligence Management: bringing intelligence into product creation and helping small and medium-sized retailers get their products onto larger marketplaces such as Amazon.&lt;/p&gt;

&lt;p&gt;What started as a small product information manager for pushing a Salesforce Commerce Cloud catalogue&lt;br&gt;
to Amazon, built on Sanity, then morphed into a bigger question: why could Sanity itself not become the&lt;br&gt;
source of truth for products? We initially imported around 10,000 products into Sanity; the current demo&lt;br&gt;
dataset contains approximately 4,500 sample products. For now, we have chosen one channel-&amp;gt; Amazon-because&lt;br&gt;
it is the largest marketplace.&lt;/p&gt;

&lt;p&gt;An AI agent audits each product against Amazon channel rules &lt;strong&gt;that live in&lt;br&gt;
Sanity as content&lt;/strong&gt;, a Sanity Workflow moves the product through review, and an&lt;br&gt;
approved product renders its own promo video. Fixing a product's issues&lt;br&gt;
re-audits it, which advances the workflow, which re-renders the video.&lt;/p&gt;

&lt;p&gt;Built for the dev.to Sanity Challenge, Path Two — &lt;em&gt;Vibe-Code Something Strange&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Sequence diagram showing the product import, audit, video rendering, and publishing workflow&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhur7ar1uszbbny3ucw4t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhur7ar1uszbbny3ucw4t.png" alt="Sequence diagram showing the product import, audit, video rendering, and publishing workflow" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  What is actually interesting here
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Audit rules are content, not code.&lt;/em&gt;&lt;/strong&gt; &lt;code&gt;auditRule&lt;/code&gt; is a document type. The&lt;br&gt;
scoring engine fetches enabled rules at run time:&lt;/p&gt;

&lt;p&gt;A merchandiser can add a check, retune a severity, or disable a rule from the&lt;br&gt;
Studio, and the next audit picks it up-no deploy and no code change. Some rules&lt;br&gt;
genuinely require cross-field logic and belong in code rather than content for&lt;br&gt;
example, preventing luxury items from falling below a price threshold or&lt;br&gt;
flagging products priced above $10,000 outside a luxury category. These act as&lt;br&gt;
guardrails around the agent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The workflow calls out to an agent.&lt;/strong&gt; Entering the &lt;code&gt;audit-pending&lt;/code&gt; stage&lt;br&gt;
queues an effect, runs a Mastra agent through OpenRouter, and writes the report&lt;br&gt;
back as a document. The stage does not advance on a timer&lt;br&gt;
or external callback. It advances after the audit effect completes and the&lt;br&gt;
agent has written &lt;code&gt;readinessScore&lt;/code&gt; onto the product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A person and an agent advance the pipeline the same way.&lt;/strong&gt; There is no&lt;br&gt;
"workflow stage" field on the product. The workflow instance is the only record&lt;br&gt;
of where a product is, and every control in the dashboard fires an action on&lt;br&gt;
that instance rather than patching a document to fake a move.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approval renders a video.&lt;/strong&gt; Clearing review triggers a Remotion render whose&lt;br&gt;
copy comes from the audited product. The finished video writes a reference back&lt;br&gt;
onto the product, which is what advances the workflow again. Currently, this is&lt;br&gt;
a local render. I would have liked to test rendering directly into Sanity's&lt;br&gt;
Media Library, but that was outside the scope of this build. One learning: AI&lt;br&gt;
has no taste. It still needs a lot of babysitting to produce a genuinely good&lt;br&gt;
video. The current result is rough, but—heck—i am a video producers now!&lt;/p&gt;
&lt;h2&gt;
  
  
  The pipeline
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;draft → audit-pending → audit-passed → video-requested → video-ready → published
  │           │
  │           └── human-review     (score 45–49: a person decides)
  │
  └── on entry: enrich-product fills gtin, brand, condition, image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Entering &lt;code&gt;draft&lt;/code&gt; queues enrichment, because a product's score is otherwise&lt;br&gt;
capped by factual fields no copy suggestion can supply — a product with perfect&lt;br&gt;
marketing copy and no GTIN still cannot pass.&lt;/p&gt;

&lt;p&gt;Transitions out of &lt;code&gt;audit-pending&lt;/code&gt; are gated on the readiness score:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;50 or above passes.&lt;/li&gt;
&lt;li&gt;45-49 stops for human review.&lt;/li&gt;
&lt;li&gt;Below 45 returns to draft.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those thresholds live in &lt;code&gt;sanity.workflow.ts&lt;/code&gt; and nowhere else.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/YvP8RsqQ3Gg" width="710" height="399"&gt;
  &lt;/iframe&gt;
 &lt;/p&gt;

&lt;p&gt;4 Min Video&lt;br&gt;
&lt;a href="https://youtu.be/GzKICaJHi2I" rel="noopener noreferrer"&gt;https://youtu.be/GzKICaJHi2I&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pim-lite-delta.vercel.app" rel="noopener noreferrer"&gt;https://pim-lite-delta.vercel.app&lt;/a&gt; requires a login to move forward. Did not want to change the code right now. &lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;I have tried to document as best as i could, all the issues. I will update it once again after 4th Oct. After i have extracted the agent coding sessions.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SunjaySingh/PIM_LITE" rel="noopener noreferrer"&gt;https://github.com/SunjaySingh/PIM_LITE&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  My Build Process
&lt;/h2&gt;

&lt;p&gt;This is the build process I followed. First, I stuck to VS Code with the Claude&lt;br&gt;
plugin and GitHub Copilot (GHCP). On the left was Claude in chat mode; on the&lt;br&gt;
right was GHCP.&lt;/p&gt;

&lt;p&gt;I started with the contest prompt, then added the real use case of developing a&lt;br&gt;
PIM Lite solution and turned it into a specification with our architectural&lt;br&gt;
needs. I already had a CSV export of the previous Sanity product catalogue. I&lt;br&gt;
told Claude about it, and it automatically created the import and generated the&lt;br&gt;
schema from the CSV. I started with Sonnet 4.6 on low. It created the UI and&lt;br&gt;
Sanity document list, with all the CTAs wired up.&lt;/p&gt;

&lt;p&gt;Then the back-and-forth with Claude began. It behaved exactly like a junior&lt;br&gt;
developer. While testing, I saw that the workflow was not working and told it&lt;br&gt;
so. Claude replied, "Hey, you did not tell me to deploy. Here is what I have&lt;br&gt;
coded and ready." The first version was a Next.js app, but I checked it and&lt;br&gt;
pointed out that the App SDK was not being used. Then the frustrating run began.&lt;/p&gt;

&lt;p&gt;I had read about StyleX and asked it to use it. That did not go the way I&lt;br&gt;
wanted, so I took the safe route and asked it to use CSS Modules. The&lt;br&gt;
&lt;code&gt;pim-lite-spec.md&lt;/code&gt; file therefore mentions StyleX, while the code uses CSS&lt;br&gt;
Modules. I also asked Claude to replay the StyleX challenge and document what&lt;br&gt;
happened.&lt;/p&gt;

&lt;p&gt;The next challenge was Workflows and the App SDK. The workflow was not&lt;br&gt;
triggering or working end to end. I had made one rule for myself: I would not&lt;br&gt;
look up the code; I would prompt my way out of hell! I asked Claude, "Can you&lt;br&gt;
give me the three most important issues we need to fix?" It gave me a&lt;br&gt;
wonderfully deep technical brief, which admittedly went over my head. I had&lt;br&gt;
heard about effects but had never ventured into them. These were the three big&lt;br&gt;
issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The workflow engine is wired to nothing; fix the configuration.&lt;/li&gt;
&lt;li&gt;Human approval bypasses the workflow entirely.&lt;/li&gt;
&lt;li&gt;The App SDK is installed but mostly unused.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then it was back to skills and the specification. Claude created&lt;br&gt;
&lt;code&gt;spec-real-workflow-transitions.md&lt;/code&gt;, and just like that, the whole application&lt;br&gt;
changed. Everything was wired up and almost working. A few small niggles&lt;br&gt;
remained, which I worked through with more prompts. I also asked it to run an&lt;br&gt;
end-to-end test, and it tested the application using curl and a Bash script. It&lt;br&gt;
said Playwright and the Chromium CLI were not available. I installed&lt;br&gt;
Playwright, but did not manage to get a full end-to-end Playwright test running.&lt;/p&gt;

&lt;p&gt;The next challenge was the Remotion video. It was not working as expected&lt;br&gt;
because of the workflow and some bad wiring. Of course, this was not originally&lt;br&gt;
planned. The idea came after around two days of work: why not create a product&lt;br&gt;
video from the product title and whatever image was available? Again, nothing&lt;br&gt;
was planned or prescribed for the video; I let the agent give me what it wanted.&lt;br&gt;
This was more of a learning experience for me—to see when and how to guide the&lt;br&gt;
agent.&lt;/p&gt;

&lt;p&gt;I used Mastra agents to build the audit and enrichment agents. This was the&lt;br&gt;
really interesting part: it almost one-shotted the entire agent implementation.&lt;br&gt;
My reasoning is that I had previously been learning how to write agents the hard&lt;br&gt;
way. I have a set of 12 agents that I am building by hand, so I think it found&lt;br&gt;
that prior art, saw a whole subset of previously created agents, and used those&lt;br&gt;
patterns. The skills for looking up documentation also existed globally.&lt;/p&gt;

&lt;p&gt;One key mistake was not telling the agent—or updating &lt;code&gt;AGENTS.md&lt;/code&gt;—to always&lt;br&gt;
look up the documentation. Since Workflows had only just been released, it went&lt;br&gt;
all over the place. Another idea, &lt;em&gt;Pencil Down&lt;/em&gt;, was popular during the build,&lt;br&gt;
and I took it literally. However, I now firmly believe in the philosophy, with&lt;br&gt;
one adjustment: it should be pencils and paper during the planning phase.&lt;/p&gt;

&lt;p&gt;I did not plan the workflow phases or the interaction flow, and that really&lt;br&gt;
puts you to the test. Yes, the cost of code is now insignificant. I think the&lt;br&gt;
total cost was less than $20; previously, building just an SFCC sync engine took&lt;br&gt;
one full-time engineer almost three weeks. Agents do not have taste or&lt;br&gt;
finesse—that is what still needs pencils and paper. No question about it.&lt;/p&gt;

&lt;p&gt;This was a totally vibe-coded solution: I did not look at the code. I only&lt;br&gt;
looked at the CSS files, the Mastra agent file, and the product schema. After&lt;br&gt;
that, I never looked at the code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/agent_sessions/claude-code-session-8vqlio"&gt;https://dev.to/agent_sessions/claude-code-session-8vqlio&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Piece&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sanity.workflow.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The one workflow definition. Must be at the repo root — the Workflows CLI resolves its config by filename against the root only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;components/pim-dashboard/effectHandlers.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Handlers the engine dispatches queued effects to, keyed by effect name.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;agent/enrich.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deterministic enrichment — derived GTIN, house brand, condition, catalogue image.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;agent/auditAgent.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rule engine (&lt;code&gt;runRules&lt;/code&gt;, &lt;code&gt;calcScore&lt;/code&gt;) plus a Mastra agent with &lt;code&gt;fetchProduct&lt;/code&gt; and &lt;code&gt;writeAuditReport&lt;/code&gt; tools.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;components/pim-dashboard/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The dashboard. Reads and writes through the Sanity App SDK, so it runs in either host.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remotion/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A 15-second, 1920×1080 promo composition, rendered locally for the demo.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sanity/schemas/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;product&lt;/code&gt;, &lt;code&gt;auditRule&lt;/code&gt;, &lt;code&gt;auditReport&lt;/code&gt;, &lt;code&gt;productVideo&lt;/code&gt;, &lt;code&gt;colorMapping&lt;/code&gt;, &lt;code&gt;sizeMapping&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Architecture diagram
&lt;/h3&gt;

&lt;p&gt;PIM-Lite architecture across the Next.js application, Mastra agents, automation services, and Sanity platform&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fumk48bn8q1calunjju7f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fumk48bn8q1calunjju7f.png" alt=" " width="800" height="773"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The dashboard mounts twice
&lt;/h3&gt;

&lt;p&gt;The same components run in two hosts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/dashboard&lt;/code&gt;&lt;/strong&gt; — a standalone App SDK application with its own login
boundary. Client-only: the App SDK store has no server snapshot, so the route
loads it with &lt;code&gt;ssr: false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inside the Studio&lt;/strong&gt; — registered as a custom tool, where &lt;code&gt;SanityApp&lt;/code&gt; derives
its config from the workspace.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Workflow controls currently require the Studio host.&lt;br&gt;
&lt;code&gt;@sanity/workflow-studio&lt;/code&gt;'s &lt;code&gt;useWorkflowEngine&lt;/code&gt; reads Studio source context; the&lt;br&gt;
App SDK equivalent needs an engine assembled with &lt;code&gt;createEngine&lt;/code&gt; and its own&lt;br&gt;
per-resource client routing, which is not built yet. The standalone host renders&lt;br&gt;
everything else and shows a clear no-engine state for the stage controls.&lt;/p&gt;
&lt;h2&gt;
  
  
  Running it - Sanity Project Details
&lt;/h2&gt;

&lt;p&gt;Requires Node ≥ 20.9 (see &lt;code&gt;.nvmrc&lt;/code&gt;); the Workflows CLI and several scripts use&lt;br&gt;
flags that do not exist on older runtimes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nvm use
pnpm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;.env.local&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;NEXT_PUBLIC_SANITY_PROJECT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dkhhaxxy
&lt;span class="nv"&gt;NEXT_PUBLIC_SANITY_DATASET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="nv"&gt;SANITY_API_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...              &lt;span class="c"&gt;# Editor role — the agent writes back&lt;/span&gt;
&lt;span class="nv"&gt;SANITY_WORKFLOW_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...        &lt;span class="c"&gt;# shared secret for the effect endpoints&lt;/span&gt;
&lt;span class="nv"&gt;GOOGLE_GENERATIVE_AI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...
&lt;span class="nv"&gt;OPENROUTER_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...
&lt;span class="nv"&gt;NEXT_PUBLIC_APP_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm dev                 &lt;span class="c"&gt;# app at :3000, Studio at /studio, dashboard at /dashboard&lt;/span&gt;
pnpm studio              &lt;span class="c"&gt;# standalone Studio at :3333&lt;/span&gt;

pnpm import              &lt;span class="c"&gt;# import the SFCC CSV&lt;/span&gt;
pnpm seed                &lt;span class="c"&gt;# colour + size mappings&lt;/span&gt;
pnpm seed:rules          &lt;span class="c"&gt;# the audit rules&lt;/span&gt;

pnpm workflow:check      &lt;span class="c"&gt;# validate the workflow definition&lt;/span&gt;
pnpm workflow:deploy     &lt;span class="c"&gt;# deploy it&lt;/span&gt;

pnpm workflow:start &lt;span class="nt"&gt;--sku&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;SKU123   &lt;span class="c"&gt;# put a product into the pipeline&lt;/span&gt;
pnpm workflow:start &lt;span class="nt"&gt;--limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10     &lt;span class="c"&gt;# …or a batch&lt;/span&gt;
pnpm workflow:drain &lt;span class="nt"&gt;--watch&lt;/span&gt;        &lt;span class="c"&gt;# run queued effects headlessly&lt;/span&gt;

pnpm audit:batch         &lt;span class="c"&gt;# audit the catalogue from the CLI&lt;/span&gt;
pnpm remotion:studio     &lt;span class="c"&gt;# preview the promo composition&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;workflow:deploy&lt;/code&gt; and &lt;code&gt;workflow:check&lt;/code&gt; source &lt;code&gt;.env.local&lt;/code&gt; themselves and map&lt;br&gt;
&lt;code&gt;SANITY_API_TOKEN&lt;/code&gt; onto &lt;code&gt;SANITY_AUTH_TOKEN&lt;/code&gt;, which is the name the Workflows CLI&lt;br&gt;
actually reads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Styling
&lt;/h2&gt;

&lt;p&gt;Styling was an adventure. After reading the Twitterverse and watching videos,&lt;br&gt;
StyleX was the choice. But after spending two days on it, I gave up. The agents&lt;br&gt;
were not able to get it working, so I moved to trusted CSS Modules. I asked&lt;br&gt;
Claude to summarize everything it tried and explain the issue in&lt;br&gt;
&lt;code&gt;docs/devchallenge.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The dashboard uses CSS Modules. Design tokens live in&lt;br&gt;
&lt;code&gt;styles/tokens.module.css&lt;/code&gt; as custom properties on a &lt;code&gt;.tokens&lt;/code&gt; class, applied at&lt;br&gt;
the dashboard root, with &lt;code&gt;styles/tokens.ts&lt;/code&gt; mirroring the same names as&lt;br&gt;
&lt;code&gt;var(...)&lt;/code&gt; strings for inline styles and data objects. They are declared on a&lt;br&gt;
class rather than &lt;code&gt;:root&lt;/code&gt; because the standalone Studio has no root layout in&lt;br&gt;
which to import a global stylesheet. &lt;strong&gt;Update both files together.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Base classes are ordered before their modifiers in every &lt;code&gt;.module.css&lt;/code&gt;: CSS&lt;br&gt;
Modules resolve by stylesheet source order, not by the order classes are listed&lt;br&gt;
on the element. &lt;code&gt;docs/devchallenge.md&lt;/code&gt; is the full write-up of that migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Known gaps
&lt;/h2&gt;

&lt;p&gt;This is a challenge submission, not a production system.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Video storage is local-POC only.&lt;/strong&gt; Rendered MP4s are written to
&lt;code&gt;public/videos/&lt;/code&gt; on the machine running &lt;code&gt;next dev&lt;/code&gt; (or whatever server
process handles the effect route) and served back as a plain &lt;code&gt;/videos/&amp;lt;file&amp;gt;&lt;/code&gt;
URL — the binary itself never touches Sanity's Media Library, only the
&lt;code&gt;productVideo&lt;/code&gt; document's &lt;code&gt;videoUrl&lt;/code&gt; string does. That means the video is
only reachable as long as that filesystem and that Next.js server are the
ones serving requests. &lt;strong&gt;This does not work on serverless deployments&lt;/strong&gt;
(Vercel, Lambda, or any platform with an ephemeral/read-only filesystem or
multiple instances): a render on one invocation writes to storage that the
next request, or a different instance, will not see, and the file will not
survive a redeploy or a cold start. Shipping this beyond a local demo needs
real object storage (Sanity assets, S3, etc.) behind &lt;code&gt;videoUrl&lt;/code&gt; — which is
deliberately out of scope here.&lt;/li&gt;
&lt;li&gt;Product images are URL strings rather than Sanity image assets.&lt;/li&gt;
&lt;li&gt;Remotion renders via the local CLI; production would use Lambda.&lt;/li&gt;
&lt;li&gt;Existing catalogue products do not automatically get workflow instances.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>sanitychallenge</category>
      <category>sanity</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
