All work

Strivelabs · Design engineer · Oct 2026

Sharing artifacts in Strive

Agent-written pages in Strive could only be opened inside the workspace. Public links let anyone with the link open one without signing in, taking after Claude’s and Lovable’s share. I built it as an add-on, not a new system: one table, two routes and one check on top of what already shipped.

My part
The design doc and the frontend, with the backend paired with Claude Code
Built in
Two days, from a whiteboard sketch
Status
Live for every customer
Link
strivelabs.ai
Live today: open Share, glance at Export, pick Anyone with the link, copy.

Backstory

A whiteboard and one sentence

  • Artifacts are the pages Strive’s agent writes in chat: reports, guides, plans. Our CTO sketched two flows on a whiteboard, signed in and shared, and the brief was one sentence: let anyone with the link open one, without signing in, as an add-on to the artifact and file work that already shipped.
  • I own the frontend, and backend isn’t my home ground. So I paired with Claude Code on the investigation, the design doc and the server side, and kept the brief, the product and scope calls, the testing and the redesign. Design first, reviewed, then build.

The find

The frame never had a session

  • Before drawing any boxes, I read what had already merged. Artifacts render in a sandboxed frame, so the browser never sends it the login cookie anyway. Every request it makes carries a signed token in its path.
  • So a public viewer is just someone without a session, and the frame never had one either. The link only needs another way to hand out that token. The page route, the image route and the sandbox stay as they are.
Everything after the slug runs on routes that already shipped. Sharing added one table, two routes and one check.

Design call · the link

  • My first question: could the Clerk session token be the share credential, or did we need our own JWT? Neither. A session token would hand out the sharer’s whole account, and a JWT can’t be turned off without a table anyway.
  • So the link is a random id in a row, the way ChatGPT and Claude do it. When our CTO asked how the string is made, I compared it with UUIDs, nanoid and short codes. A link anyone can forward is a password, so its length is a security setting, not a style choice.
Short, boring and revocable. Two credentials, each doing one job.

Design call · images

Scope it to the page, not the workspace

  • The sketch had shared links check that an image belongs to the workspace. It looks like a fair simplification. But file ids count up, so one public link could have walked from asset/1 to asset/N: every image the workspace ever uploaded.
  • So images stay checked against the page they belong to, the check that already shipped. I put each correction in the design doc next to the code it came from, so it read as a fact, not an opinion.
A simplification that looked fair was an open door.

Design call · the table

One table for every shareable thing

  • The first draft had a share table just for artifacts. Reviewing it, I asked for one generic table instead, the way our relations table already works, because reports and chats would want sharing next.
  • Each kind plugs in a small resolver: does it exist, what does a version mean for it, and what does a stranger see. Reports and chats can join without a migration or new routes. Generic one step ahead, and no further.
The table knows a type and an id. Each kind answers the rest.

Before · after

Access is a state, not two buttons

  • The first version worked. Then I used it, and changed the model rather than polishing it. Claude and ChatGPT keep sharing and exporting under one control, so Share and Export became two tabs of one, with room for people and roles later.
  • “Create public link” and “Stop sharing” became General access: members only, or anyone with the link. Picking one turns the link on, and picking the other turns it off. The bare share glyph became a small labelled Share button.

Before

After

Before: a button to make a link, beside separate Share and Download icons.
Members only still has something to copy: a private link.
Export says it plainly: images won’t load in the downloaded file.

The other side

What a stranger sees

  • A shared page is bare: the title, “Shared from” and the workspace’s name, the page itself and a small “Made with” mark. No sign-in, no chat, no app around it. The name builds trust and costs nothing.
  • A dead link is a first impression too, for someone outside the company. So the plain error became a branded page that says why in one line and offers a way in.
  1. Title, and who shared it. “Shared from” and the workspace’s name.
  2. Made with. A small mark, and the partner’s own on a white-label domain.
  3. The page, untouched. The same stored page, images included, in the same sandbox.
Opened in a private window. Nothing else from the app comes along.

Before

After

A plain error, then a page with the brand, one line of why, and a way in.

The details

Small calls, said out loud

  • Nobody sees most of these. Each one is still a decision, so each has a reason written down.
  • Pinned to what you shared

    A link shows the version it was shared at. Later edits from the agent stay private until someone moves the link to the new version.

  • A dropdown I didn’t build

    Claude uses a dropdown for General access. Inside our popover, a dropdown opens a layer of its own and closes the popover on the first click. Two radio rows do the same job.

  • Arrow keys that can’t publish

    A review suggested arrow keys between the two choices. Radios pick as focus moves, so one stray key press could make a page public. I said no.

  • Guests still get Export

    A guest can’t share, but can still download. The control shows what each person can do, and hides when there’s nothing.

  • Safe on a partner’s domain

    The dead-link page and the “Made with” mark take the partner’s own name and logo, so a white-label domain never says Strive.

  • Every dead link looks the same

    Turned off, deleted or never there: the same page, so the address tells a stranger nothing.

Smudges

Two 404s and a leaky address

  • My first local test returned a 404, and the next one broke artifacts even for signed-in people. Neither was in the code: a local proxy forwarded only a fixed list of paths, and the local render secret was empty. I added the missing paths first. A review then asked why the proxy needed a list at all, so it became one catch-all, and the next new route can’t quietly 404.
  • My own code review caught a real one. Product analytics recorded every page address, so the share link went along with it, and anyone with analytics access could have opened shared pages. The link is now masked before any event leaves the browser.

What dried

What I’d keep

  • MissFixed the proxy by adding the two missing paths.

    LessonFix the mechanism, not the symptom. A list of paths will always miss the next one.

  • MissBuilt the first popover around “Create public link” and “Stop sharing”.

    LessonMake access a state you set, not two actions you hunt for.

  • LessonRead the code before drawing boxes. Half the feature had already shipped.

  • LessonSay no on purpose. Every review finding I skipped has a one-line reason, usually that it would change something people rely on.

The best feature was the one already half-built.

Back to all work