Rowstr Changelog
Latest updates and improvements from Rowstr.
Custom roles, activity log, time tracking, pricing gates
Custom roles — granular permissions for every team member
A new Roles page at /agency/roles gives owners and admins fine-grained control over what each role can see and do.
- 32 permission keys organized by feature area: models, todos, chat, feed, marketing, members, billing, media, activity. Every permission is enforced server-side via
requirePermission()middleware on every relevant tRPC router — client-side gating alone would protect nobody. - 6 built-in roles: owner, admin, va, chatter, assistant, model. Owner and admin carry a wildcard
*that bypasses all checks. - Custom roles: create any named role with any combination of permissions via the dedicated UI. A Model custom role is seeded automatically on agency creation.
- Server-side enforcement wired into:
todo,chat,media,saved-reel, andlink-in-biorouters. Each mutation checks the caller's permissions before executing. - Mobile sidebar gating: navigation items, action buttons, and feature links respect the caller's permissions — a chatter won't see billing or members management on any screen size.
- Invite-scoped roles: team invites (email and link) respect the invitable roles. Only Admin and Model roles can be invited for now; VA, Chatter, and Assistant will follow.
Activity log — every action, tracked
Every meaningful action across the agency is now recorded and visible.
- Per-member view: open any member's sheet to see a chronological feed of their actions — created a model, moved a todo, uploaded media, sent a message, changed settings.
- Entity-name prominence: activity entries show what was acted on (model name, todo title, media filename) as a tappable link directly in the feed.
- Luxe sheet UI: each activity type has its own Lucide icon and color treatment. Filter by activity type inline.
- Comprehensive tracking: model CRUD, todo status changes, media uploads/deletions, member joins/leaves, role changes, invitation outcomes — all captured with actor, target entity, and timestamp.
- Retention: activity history is persisted and queryable, so you can audit past work even after members leave the agency.
Time tracking — shift clocking for VA and chatter teams
A shift clocking system lets VA and chatter team members log their hours.
- Clock in / Clock out: one-tap shift start and end from the Time page or sidebar, with automatic duration calculation.
- Manual corrections: admins and owners can adjust shift times, add notes, and override clock-out times for any member.
- Agency supervision at
/agency/shifts: see who's currently clocked in, review all shifts across the team, filter by date range and member. - Role-aware UI: non-admin members see only their own shifts; admins/owners see the full team view.
- Shift history with total hours per day, week, and custom range.
Pricing gating — feature flags and upgrade CTAs
A feature-flag system ties capabilities to plans, with upgrade prompts throughout the UI.
requireFeature()middleware: server-side enforcement on any tRPC procedure — if the org's plan doesn't include a feature, the call is rejected before any business logic runs.requirePlan()middleware: gate entire routers or individual procedures behind a minimum plan tier (Free, Starter, Pro, Agency).- Feature flag constants: a centralized registry of every feature-flag key, plan-to-feature mapping, and per-plan limits (models, tracking links, competitor lookups, storage).
- Upgrade CTAs: dashboard banners, sidebar upgrade chips, and inline upgrade buttons replace the previous "silent failure" on plan-limit hits. Each CTA links to the pricing page with the correct plan pre-selected.
- Plan-aware limits enforced server-side: model count, tracking link count, competitor daily budget, storage caps — all checked before any create/upload mutation.
PostHog analytics — product-wide event tracking
Comprehensive analytics tracking across the entire application.
- ~200 named events across every feature area: models, todos, chat, media, calendar, feed, links, billing, invitations, roles, settings, auth, onboarding.
- Every event is grouped by
agency(organizationId) so per-agency product usage can be analyzed. - Client and server tracking: browser events (page views, clicks, interactions) through
posthog-js, server events (API calls, mutations, cron jobs) throughposthog-node. - Separate PostHog project for Rowstr (not the Substy project) — no data leakage between products.
Sidebar — accordion navigation
The sidebar now groups links into collapsible accordion sections.
- One open at a time: opening a section automatically closes any other open section — no more scrolling through a wall of links.
- Dedicated sections: Models, Content, Team, Agency, Links, and Settings each live in their own accordion panel.
- Mix of link and collapsible items: top-level one-links (Dashboard, Calendar, Feed) sit above the accordion, grouped features sit inside.
- Upgrade CTA chip sits at the bottom of the sidebar, always visible, linking to billing.
- Auto-collapse fix: navigating to a different section closes the previous one reliably — no more phantom open panels after route changes.
Link-in-Bio: builder, tracking & analytics
Link-in-Bio: a builder for every model
A complete bio-link page now lives next to each model. Build it from Links → Bio. The public page is served at rowstr.com/lib/<slug>, or on one of your custom short domains. New pages start as Draft until you flip them Live.
- Linktree-parity editor with avatar, cover image, display name, a 280-character bio, and a row of social links you can place above or below the content.
- 3 header layouts (Classic, Hero, Banner) with a smooth bottom gradient that blends the cover into the page background. The avatar doubles as the cover when no cover is uploaded.
- 12 one-click themes across light, dark and gradient families, each shown as a real mini-preview (with the header layout it uses) and a couple flagged as Top picks. Granular control of background, buttons, colors and alignment is there too.
- Font choices including Rowstr's own General Sans, plus Inter, Poppins, Space Grotesk, Sora, Playfair, DM Serif, Bebas Neue and Caveat.
- Live phone preview beside the editor. Toggle it on desktop or mobile, and it tracks every edit in real time through optimistic updates.
- Publish as a Live / Draft toggle. Flip a page offline and visitors see a branded "page unavailable" screen instead of a 404.
- Undo / redo for settings, with the active editor tab kept in the URL (
?view=) so a refresh or a shared link lands you on the same panel.
Links: inline, auto-saving, pairable
- Inline expand-to-edit in the Linktree style instead of a modal. Open a row, edit in place; changes save automatically (debounced, so trying a few options doesn't spam the server), with an Add link / Done button to confirm. New links open below the list.
- Optional title. Leave it blank for a clean icon-only button, with the icon centered.
- Drag to reorder. Drag a card onto the right edge of another to pair them into a side-by-side 5:3 row, and use the hover unlink chip to split the pair apart again.
- Display modes picked from real mini-renders: button, card, or wide.
- Icon picker in a categorized popover (Social, Video & music, Contact, General) with a per-link tint (Color / White / Black, real SVG variants) and a one-tap custom image upload.
- Schedule visibility with a calendar range picker: show a link only between two dates.
- Avatar and cover cropping in a dedicated modal that defaults to a square crop, with a live preview.
Tracking, built into every link
The bio is not a dead end. It feeds the same attribution system as the Links dashboard.
- Bind any bio link to a Tracking Link so its clicks roll into that model's existing per-model attribution alongside Instagram metrics. No separate tooling.
- Attach a custom short domain to the whole page, with the same one-click DNS and SSL setup as Tracking Links.
- Accurate geo on every event. Views and clicks read Cloudflare's
cf-ipcountry(with a Vercel fallback), so country data stays correct even behind the CF tunnel. - Public pages require no auth.
/lib/<slug>and its click and view endpoints are open, while everything in the editor stays org-scoped.
Analytics: a live dashboard per page
Every bio page has its own Analytics view at /links/bio/<id>/analytics, refreshing every 30 seconds.
- Four stat cards, each with an animated counter: Profile views, Link clicks, Total interactions (views plus clicks), and Engagement rate (clicks divided by views).
- Real-time activity with per-minute sparklines for visits, clicks, and total activity over the last 60 minutes, ticking every minute.
- Traffic overview area chart with peak day, active-day count, and average per day.
- Breakdowns for top links, top referrers, devices, and top countries.
- Visitor world map. A dotted map where marker size scales with click volume and the hottest countries pulse.
- Date range with 7d, 30d, and 90d presets plus a custom range picker, all persisted in the URL. A clean empty state shows until your first visitor lands.
Three free OnlyFans tools
Three public tools now live at the site root, with no signup and a short generation animation on each:
- Name generator for on-brand stage-name ideas.
- Revenue calculator to model your earnings.
- Script generator that builds a real 10 to 12 message priced sales sequence.
All three share the marketing nav, footer, and FAQ, and sit in the same max-w-7xl container as the rest of the site.
Blog & landing
- 11 new blog posts plus one rewritten, deepening the content library.
/forniche landing pages, a Solutions nav entry, and a footer link hub tying them together.- New homepage hero positioning: "Run your creator agency without the 8-tool stack."
Team & invites
- Team invites (email and link) are scoped to the Admin and Model roles for now. VA, Chatter and Assistant are coming soon.
Billing
- The free trial is now 14 days (up from 7).
- Affiliate recurring commissions are capped at 12 months.
Reliability & performance
- Database resilience. Transient
ETIMEDOUTandECONNRESETerrors now retry on every query with progressive backoff, up to 4 attempts. - OAuth fixes. Force IPv4-first DNS so Google sign-in no longer times out, and pin undici to v6 for Node 20 compatibility.
- Chat and dashboard performance. Paginated conversation list, the dashboard
modelStatsN+1 fused into one query, and a single shared Redis subscriber across SSE streams.
Security
- Admin endpoints return 404 to non-admins rather than 403, so scanners cannot confirm the surface exists.
- Org membership is enforced on every org-scoped upload.
- Closed a race on
invite.joinWithTokenmodel linking, added an IP rate-limit to the public mutation, and stopped logging Instagram webhook payloads.
Tracking Links, Telegram digest, 2FA, trials
Tracking Links — new dashboard
A new top-level Links page lets you create trackable short links, attach them to a model, and see clicks aggregated across your whole roster. Three tabs: Overview, Links, Domains.
- Custom short domains: bring your own (e.g.
mdl.so/abc123) and we handle the DNS + SSL provisioning. One-click setup, no DevOps. - Per-link metrics: total clicks, last-24h vs last-7d delta, top referrers, browser / device / country breakdown, and a time-series chart.
- Per-model attribution: every link can be scoped to one of your models so click data shows up alongside their Instagram metrics on the model overview.
- Date range picker with up to 365 days of history.
Instagram analytics — richer model overview
The model overview page is reworked around a new Analytics tab:
- Followers / following / engagement rate over time.
- Audience demographics: gender, age bracket, top cities and countries.
- Recent posts strip with per-post likes, comments, and reach.
- Disconnected accounts no longer disappear — they stay visible (greyed out, "Disconnected" label) so historical snapshots and media remain accessible. One click to reconnect.
Telegram digest — bot in your channel
Connect a Telegram channel to your agency and get scheduled or on-demand summaries of what's happening across your roster.
- Setup in two clicks: type
/setup CODEto the bot and you're linked. - Cadence: daily, weekly (multi-day picker), or monthly. Pick the time in 30-min slots and override the timezone per digest.
- What's in the digest: clicks for the period, todos by status, Instagram follower delta. Each section is toggleable.
- Custom HTML templates: write your own message with autocomplete on variables —
{date},{clicks.total},{todos.published},{ig.followersDelta}, and more. - On-demand commands in the chat (no scheduling needed):
/report— same render as the daily digest/links— total clicks 24h vs 7d + top 10 links/todos— counts by status + next 10 ready-to-publish titles with model names/ig— Instagram snapshot per model
Two-factor authentication
Enable 2FA on your account from the new Security section in your profile.
- TOTP-based, works with Google Authenticator, 1Password, Authy, any standard authenticator app.
- One-time backup codes generated at enrollment — download and store them somewhere safe in case you lose your phone.
- Set or change your password from the same panel.
Trials and billing banners
A real trial system replaces the previous "everything is enabled" default.
- New signups get a 7-day free trial on every plan, no credit card required.
- Trial countdown banner appears at the top of the dashboard as the trial nears its end, with a one-click upgrade.
- Payment, storage, and model-count warnings surface the same way — no more silent failures when a card declines or you hit a plan cap.
Media — tags and favorites
The drive picks up two long-requested workflow features:
- Tag any media item with one or more labels. Filter the drive by a single tag or combine multiple.
- Favorite an item with a single click on hover — the favorites view stays snappy as libraries grow.
Legal pages
- New
/security— public trust page documenting encryption, data residency (EU), payment handling, audit logging, 2FA, and our vulnerability disclosure policy. - New
/dpa— full GDPR-compliant Data Processing Agreement covering sub-processors and transfer mechanisms. /privacyand/termsrefreshed and unified under a shared layout with cross-navigation between all four legal pages.
Navigation — back-button now works the way you'd expect
Browser back/forward and shareable links now respect the active tab or view everywhere in the app.
- Switching from Calendar Month view to Week view, then hitting back, returns to Month — not the previous page.
- Deep-linking to a specific tab (e.g.
/notifications?tab=telegram) now lands you exactly there. - Applies to: Notifications, Model overview, Tracking Links, Todo (table / kanban), Calendar (month / week / 3-day / day), Drive (grid / list).
Landing — refreshed
- The features grid on the homepage now shows the four newly shipped capabilities: trackable short links, custom link domains, Instagram/TikTok integration, account analytics.
- The "Replaces" comparison adds Bitly (for tracked links) and native Instagram/TikTok analytics — six tools replaced in total.
Empty states
A consistent empty state pattern landed across the app — same icon, same proportions, same primary action — wherever a list, grid, or panel can be empty (chat, drive, models, todos, links, invitations).
Chat rework — grouped messages, threads, pins, performance
One message, multiple attachments
- Single-message multi-attach: photos, videos, voice notes and text sent together now go out as one message instead of one-per-piece. Order is always media → voice → text inside the bubble.
- Attachment queue in the composer: pick several images at once with the paperclip, stack voice notes, write a caption, then hit Send. Each tile has a hover-only X in the top-right that disappears during upload.
- Drag-to-reorder the queue — no grip handle, just drag the tile; a primary-colored vertical bar shows the drop target.
- Images lightbox: click any image in the thread (chat or thread panel) to open it full-screen, object-contained, with
cursor-zoom-inaffordance. - Image grid layout inside a bubble: 1 → full, 2 → 2-col, 3+ → 3-col
aspect-squaretiles. - Voice-note grid inside a bubble: multiple voice bubbles wrap side-by-side up to ~3 per row.
Voice recording — Slack-style
- Popover recording UI above the mic button: live timer, animated waveform, and an X to cancel.
- Mic button becomes the validate button while recording — the icon swaps from 🎤 to ✓, click to confirm, click ✕ (or press
Escape) to discard. - Enter confirms from the keyboard;
Escapecancels. - Preview before sending: recorded voices land in the attachment queue as a full
VoiceMessageBubble— play / pause, scrub the waveform, remove from the queue, stack another. - "Microphone blocked" popover is now dismissable: click the mic again (or outside) to close, state resets cleanly.
- MIME handling:
audio/webm;codecs=opusnow correctly passes server validation — the codec suffix is stripped before the allowed-types check.
Rich link cards (IG / TikTok reels)
- Preferred data source is
SavedReel: when a reel link is posted in chat and the URL matches a reel already in one of the user's orgs, the preview card uses its stored metadata (handle, caption, likes, comments,videoUrl) instead of scraping the public HTML. More reliable, and the video actually plays. - Inline playback: hover to prefetch + autoplay muted after 400ms; click to zoom in a small dialog with native controls.
- Meta layout overhaul:
@handle+ posted date on one row, ❤ likes / 💬 comments on the next, caption underneath. No more "ugly pink" Instagram pill — just a neutral dark badge. - og:description parser for Instagram: splits the
"13K likes, 76 comments - keo on October 26, 2025: "Nutted""format into structured fields server-side. - Save + Recreate CTAs removed from the card for now.
Threads vs replies — properly separated
- A new
isThreadReplyflag on every message distinguishes:- Reply (from the main composer): appears in the main conversation with the quoted parent above it, does not open or belong to a thread.
- Reply in thread (from the thread panel): lives only inside the thread, never shown in the main conversation.
getMessagesfilters out thread replies,getThreadonly returns thread replies.- Thread reply counter on the root message (
N replieswith an icon chip) — filtered to thread replies only. Click to open theThreadPanel.
Pinned messages
- Pin / Unpin any message via a new Pin action next to reply / thread / reactions. Works on your own messages and on others'. Optimistic update — the pin flips instantly with a rollback on error.
- "Pinned by you" / "Pinned by [sender name]" indicator above the bubble with a height-animated reveal.
- Pinned banner at the top of the conversation: collapsed row showing
{count} pinned+ preview of the most recent pin; Show all expands to a list with jump-to + hover Unpin. - Jump-to-message flash: clicking a pinned item or a reply quote scrolls the target into view and runs a subtle
bg-primary/10pulse (@keyframes chat-flash, 1.2s) — no more hard ring.
Message actions — polish
- One set of actions per burst: when you send a photo + voice + text together, only the last message in the burst shows the action row — not three redundant copies.
- Hover-only with pointer-events handling: actions fade in on message hover and fade fully out after, no sticky hover-state bug.
- Tooltips on every action (Reply, Reply in thread, Pin / Unpin, Delete) via a local
ActionTooltipwrapping each button in its ownTooltipProvider— no more "Tooltip must be used within TooltipProvider" runtime error. - Destructive hover (Delete) properly overrides the icon variant's primary hover — red wash on hover instead of green.
- Unified icon-button sizing:
Buttonnow has a propericonvariant (!h-6 !w-6 !p-0, rounded, muted → primary hover). All chat icon buttons — search toggle, emoji picker, attach, reel picker, mic, send, message actions — share it. Fixes a long-standing bug where default size classes were adding padding that broke hit-boxes and made hover states leak.
Scroll + loading
- Pinned-to-bottom on load: a
ResizeObserveron the messages container keeps the thread pinned to the bottom while images/attachments finish laying out, instead of leaving a half-screen gap on refresh. - Jump-to-latest floating button (bottom-right chevron) appears the moment you scroll up — click to smooth-scroll back to the end. Fades in/out via framer-motion.
- Paginated 20 at a time (was 30): the initial query is lighter and the IntersectionObserver on the top sentinel fetches older pages as you scroll up.
- Dropped eager reply-preview:
getMessagesused to embed up to 3 sender previews per message for the old "avatar stack" on thread pills. That's ~3×N extra joins per page — gone. TheThreadInlineSummarynow shows a single icon +N repliestext; the full thread loads lazily only when you click.
Message-level tweaks
- Burst grouping: same-sender messages within a 5s window stack tighter (
mb-pxbetween them) — gives a visual cluster without a DB change. - Fix: when a message has a reply quote, the previous message now correctly closes its group with a
mb-3gap so the reply card doesn't feel cramped. - Thread panel: input and header heights now match the main conversation (
h-14, icon-only Send button). - Voice message preview in the queue uses the real
VoiceMessageBubblecomponent so you can actually play it before sending — not a placeholder. - Unused descriptive comments removed from recent edits per project convention.
Schema
Two new migrations applied:
20260420103000_add_message_attachments_and_pin— addsMessage.attachments Json?,Message.pinnedAt DateTime?,Message.pinnedById String? → User, with an index on(conversationId, pinnedAt).20260420120000_add_is_thread_reply— addsMessage.isThreadReply Boolean @default(false)to separate thread replies from inline replies.
API
- New routes:
chat.togglePinMessage,chat.listPinnedMessages. chat.sendMessageacceptsattachments: [{ mediaKey, mediaType, duration? }]andisThreadReply?: boolean.chat.getLinkPreviewreaches intoSavedReelbyshortcode/mediaPkbefore falling back to scraping, and its cache guards against pre-change shape./api/upload(category: voice) now normalizes MIME (audio/webm;codecs=opus→audio/webm) before the allowed-types check.
Chat v2, comments, notifications, and a single todo sheet
Chat — reactions, voice, threads, search
- Emoji reactions on every message with a quick-pick popover (12 common emojis) and live SSE sync — pill toggles optimistically, aggregates by emoji with a count and a "you reacted" highlight. Own reactions read as a subtle tinted pill, sized to fit small messages. Spring animation on add / remove.
- Voice notes end-to-end: record from the composer (WebM/Opus with a Safari fallback to
audio/mp4), 5-minute cap, cancel or send. Playback bubble with a deterministic waveform, scrubbable timeline, and proper HTTP Range streaming from R2 for instant seeking. - Slack-style thread mode: every message with replies gets an inline summary next to its timestamp — stacked avatars of the 3 most recent repliers, a clickable "N Replies" pill, and the time. Opening a thread swaps the right sidebar for the
ThreadPanel(shareable via the?thread=URL param). The main conversation's inline reply preview is preserved. - In-conversation search (
⌘F): overlay search with debounced results, keyboard navigation (↑ / ↓ / Enter / Esc), and flash-highlight scroll when clicking a result. Doesn't hijack global⌘Foutside an active conversation. - Rich link cards for Instagram and TikTok URLs in messages — detected automatically and rendered as a preview card with thumbnail, author, caption snippet, and two CTAs: Save to feed (writes to
SavedReelwhen Instagram) and Create recreate todo (creates a todo withtype: "recreate"pre-filled). - Search button in the conversation header for discoverability, in addition to the keyboard shortcut.
Chat — composer
- Media attachments (images + videos): attach via a paperclip, preview above the input (thumbnail + filename + size + remove), write a caption, then send. The file is only uploaded to R2 when you hit Send — removing the attachment is a zero-cost operation.
- Emoji picker in the composer: a 96-emoji grid next to the mic / reel / attachment buttons. Works in the main input and in the thread reply composer.
- Reel picker — the composer's "film" button opens the same
FeedPickerlibrary used elsewhere. Multi-select reels and insert their URLs into the message; the rich link card renders them inline. - Voice mic permission UX: the button is always clickable. Denied or non-HTTPS origins show a help popover with instructions ("click the lock icon → allow microphone → reload") and a Try again action. An insecure origin now reports HTTPS required instead of silently failing.
- Thread reply input matches the main composer — same height,
@mentions with a popover, emoji picker, Enter to send (Shift+Enter for newline). - Unified action button styling: hover-only tint (transparent green wash on hover, red for destructive), consistent across reply / thread-open / trash / emoji picker — replaces the mixed green-on-green pill look.
Chat — model context sidebar
- Upcoming shoots, Recent media, and Saved reels sections on the right-hand sidebar (admin view on a model DM), backed by a single consolidated
chat.getModelContextprocedure — no waterfall. - Platform icons (TikTok, Instagram, OnlyFans) replace text labels on shoot cards.
- Broken-thumbnail fallback: remote IG / TikTok CDNs frequently 404 once a post is taken down. Images and videos now swap to an icon fallback on load error so the grid stays clean.
- Error + retry state when the context query fails, with a one-click retry.
- "View all" on Saved reels opens the saved-reel library directly (see below) instead of navigating away.
Chat — layout
- Bigger thread panel: the right column expands to
384pxwhen a thread is open (vs.288pxfor model / DM info), so longer replies don't feel cramped. - Unified header heights: main conversation header and thread-panel header both locked to
h-14for pixel-perfect alignment. - Mobile back buttons and responsive collapse rules (single-column focus on narrow screens, three-column on desktop).
Saved reels — library management
- "Manage my saved reels" button on the Feed page and on the chat sidebar's Saved reels section — both open the same
FeedPickerin manage mode. - Multi-select with a floating
SelectionDock(same pattern as Drive): pick reels, preview the first three as rotated thumbnails, see a live count, and Remove them in bulk with a single action. Per-tile delete is gone in favor of multi-select for consistency. - Sort: a new combobox next to the search input — Recently added, Oldest first, Most viewed, Most liked, By handle.
- Lists: create, rename (double-click or pencil), and delete reel lists from the sidebar. Inline inputs with
⏎ save/⏎ addhints inside the field. Deletion is confirmed through a realAlertDialog(no more nativeconfirm) and explains that reels become Unsorted rather than being deleted.
Todos — single sheet, everything inline
The edit sheet is gone. Viewing, editing, and creating a todo all happen in the same Notion-style sheet.
- Inline Notion-style fields: Title, Status, Dates, Platforms, Drive folder, and Brief are clickable directly in the header. Clicks open a small popover (status picker, date + time picker with full-day toggle, platform checkboxes, drive folder tree) — no more second sheet on top of the first.
- Read-only by default, Edit to unlock the content block: the footer shows Close and Edit. Edit switches the content section to the full editor (drag-to-reorder, duplicate, upload from device, add from a saved reel, status cycle, brief inline). Close exits edit mode — or closes the sheet when you're already in view mode.
- Blank-then-open create flow: "+ New todo" creates a minimal blank todo on the server and opens the sheet directly in edit mode, so you fill everything in place. "Todo from media" in Drive does the same, with the selected assets pre-attached as content items.
- Smoother view ↔ edit transition: no more close/reopen flash. The sheet stays mounted and its content swaps in place.
- No more edit sheet (
TodoSheet.tsx): all callers (calendar, kanban, table, drive) now go through the single view sheet.
Todos — content editor
- Card-list content editor with drag-and-drop reordering (
@dnd-kit) in both create and edit modes. - Unified thumbnail dropdown on every card — one entry point for Preview, Upload from device, Pick a saved reel. Empty original cards render as a dashed upload drop-zone so the expected action is obvious.
- "Add content item" dropdown gets three options: Blank item, Upload from device (with a spinner while the file uploads), From a saved reel.
- Duplicate a content item with a small Copy button next to the trash — clones the source item (type, source reel, brief) directly below it.
- Subtle entrance animation on new cards (fade + slide-in from top).
- Visible brief textarea on each card so it's clear where to type the brief for that specific content item.
- Optimistic cache updates on add / update / delete / reorder — no more waiting for the round-trip before the UI reflects the change.
Todos — comments and activity
- Comment threads on every todo with
@mentionautocomplete — scoped to the organization. Comments live in the view sheet as a dedicated tab. - Agency-only by default: models can't see comments unless the org opts in through a new setting. The UI gates the tab accordingly.
- Activity timeline as its own tab (agency-only) — renders status changes, content additions, uploads, and comment events on a vertical rail with platform icons where relevant.
- Airtable-style tabs at the top of the content section: Details, Comments, Activity.
Todos — view sheet polish
- Bigger brief textarea in the view (resizable,
min-h-[88px], borderless) and moved to the last position in the header rows so it gets space to breathe. - Progress row realigned with the other rows — icon + label + fixed-width bar + ratio, matching Deadline / Platforms / Brief layout instead of stretching full-width.
- Drive folder helper: a small
?next to the "Drive" label explains that model uploads on the todo automatically land in the linked folder. - Model popover scroll wheel fixed — the wheel now scrolls the list instead of the sheet behind it.
- Pinned footer: the footer stays at the bottom of the sheet regardless of content length; only the content section scrolls.
Notifications — new
- In-app notification center in the sidebar rail with a bell and an unread badge.
- Deadline alerts fire automatically when a todo's due date is approaching or overdue.
- Type-based tabs (All / Mentions / Deadlines / Members) with a fluid tab-switch animation and a properly-measured popover resize.
- Per-row actions — mark as read, delete — with tooltips and muted / red hover chips. No more "Clear all" (accidental-taps risk); bulk actions moved to a safer flow.
- Skeleton loading state while the list loads, matching the rest of the app.
Activity log
- Org-wide audit trail of who did what and when — surfaced on the dashboard as an activity feed card, and under each todo as the Activity tab.
Global search (⌘K)
- Command-menu search across models, todos, media, drive folders, and chats. Keyboard-first, scoped per organization.
Dashboard
- Role-specific home for models with upload shortcuts styled like drive folders and a format that mirrors the agency dashboard.
- Fixed model route: models can now access
/dashboardwithout being redirected away.
Billing
- Stripe integration for subscription and plan management (agency billing page).
Removed
- Tags on todos — shipped earlier in the cycle, then pulled. The feature has been fully removed from UI, router, and schema; run
pnpm prisma:migrateto drop thetag/todo_tagtables. - Todo templates and recurring todos — shipped as an experiment, not enough signal to keep; removed ahead of release.
- Demo notifications — the dev-only
seedMocksmutation and the "Seed demo" buttons in the notification bell are gone.
Under the hood
- New shared
todo/sheet/module:StatusPopover,DatesPopover,PlatformsPopover,DatePickerField,EditableContentCard,ContentCardList,AddMediaButton, plus shared constants. These are now the single source of truth for todo popovers and content cards across the app. - Stricter deps on a few
useCallbackhooks so the React Compiler can preserve memoization.
Snappier chat, drive, and todos
Chat
- Cleaner conversation switching: switching between threads now fully resets the composer, reply draft, and mention state — no more leftover drafts or stale typing indicators from the previous conversation.
- Correct thread height: the chat card now fills the available space properly on every screen size.
- Smoother typing: key presses in the message composer no longer trigger unnecessary re-renders across the thread.
Todos
- URL-driven todo sheet: opening a todo updates the URL so you can share a link, bookmark it, or reopen the same todo after a reload. Closing the sheet clears the param.
- Stable pagination: the todo table no longer jumps back to page 1 when filters rerun in the background.
- Feed picker: the "pick a reel" dialog now opens from a clean state every time and doesn't carry over search or selection from the previous open.
Drive
- Faster dialogs: the folder customization and upload dialogs mount only when you open them, so the Drive page itself loads slightly faster and each dialog starts fresh.
- Media details fix: opening a second media item no longer briefly shows the previous one's dimensions or duration before updating.
- Video reset: the video player in the media detail sheet cleanly resets when you switch files.
Blog
- Tag filter in the URL: picking a category is now reflected in the URL without a full reload, and back/forward navigation keeps your filter.
Agency & settings
- No more state flashes: settings, the agency general tab, and the members page no longer briefly show stale org data when navigating into them.
Sign-up
- Server-side redirect: signed-in users hitting
/sign-upare now redirected to the dashboard on the server — no more flash of the sign-up form.
Under the hood
- A large sweep removed a class of unnecessary re-renders across chat, drive, settings, todos, and agency pages so the app feels more responsive.
- The React Compiler can now optimize more components than before, which means fewer wasted renders even on pages we didn't touch directly.
Auth, invites, and the inbox
Sign-in
- Magic-link sign-in: anyone without a Google account can now sign in by typing their email — we send them a one-click link, no password, no extra account setup. Google sign-in is still the primary path.
- Same account across methods: signing in via magic link with an email that's already tied to a Google account logs you into the same account — no duplicates.
Invitations
- Role assignments actually work: picking a role when inviting someone now sticks. Previously everyone ended up as "Member" regardless of what was selected.
- Revoked invitations stay visible: revoking an invitation no longer hides it — it moves to a "Revoked" section as a history log so you always know who was invited and when.
- Timestamps on every invitation: each row now shows when it was sent and, for revoked ones, when it was revoked.
- Revoked links can't be claimed: clicking a cancelled or expired invitation now shows a clear "Invitation unavailable" screen instead of silently letting the recipient join.
Emails
- Branded email templates: invitations, welcome, and magic-link emails all use a new layout with the Rowstr logo, brand colors, and bullet-proof call-to-action buttons that render consistently across Gmail, Outlook, and Apple Mail.
- Welcome email timing fix: welcome emails now arrive only once the recipient actually verifies their account — no more receiving one seconds before you've even clicked your magic link.
- Roomier uploads: avatar and logo uploads now allow up to 5 MB (was 2 MB) so common PNG exports don't get rejected.
Drive, top to bottom
Folders
- Customizable folders: pick a color, an icon, and optionally link a folder to a model — your drive finally looks like yours.
- Linked-model badge: folders tied to a model show a compact pill with the model's avatar and name, tinted to match the folder color.
- Persistent empty folders: create a folder without dropping files into it — it sticks across navigation and reloads.
- Smart import target: importing from inside a folder now lands the files in that folder by default.
- Instant feedback: creating a folder and editing its color, icon, or linked model updates the UI immediately.
- UX rework: folders now sit in a compact row above the media grid with a kebab menu for actions, media tiles no longer slide in when you switch folders, and the breadcrumb only appears once you're inside a folder.
Uploads
- Uploads routed through our CDN: video traffic now goes through
cdn.rowstr.com, a Cloudflare Worker sitting right next to R2. No more round-trips through the app server — your file takes the shortest path to storage. - Parallel multipart for large videos: anything over 100 MB is split into chunks and pushed in parallel so very big clips land much faster than they used to.
- Cancel any upload, any time: hover a file in the upload widget and click the X — it actually stops, no more "ghost" uploads finishing in the background after you changed your mind.
- Real progress, not a fake bar: the progress indicator now reflects bytes actually moving on the wire, not a canned animation.
- Stronger video compression: re-encodes now use a slower, higher-quality preset that trims roughly 30% more off each video with no visible quality drop.
- Tighter size estimates: the upload dialog shows a realistic post-compression size upfront, so you know before you hit "upload" whether you're about to bust your quota.
- Drive sheet fix: reopening a media item in the drive no longer gets stuck on an infinite spinner.
Org timezones
- Timezone per org: every organization can now pick its own timezone. The content calendar, todo scheduling, and the "today" line all follow it instead of UTC — so a shoot scheduled for 8am in Paris shows as 8am in Paris, not 10am or 6am depending on where you happen to be logged in.
- Inactive orgs are archived automatically: if an organization goes quiet, Rowstr now archives it in the background so your workspace stays tidy.
Faster video uploads & mobile dashboard
- Instant video thumbnails: the thumbnail appears in the drive the moment you drop a clip, instead of waiting for the server to process it.
- Real upload progress: the progress bar now reflects the actual upload, not a fake animation — especially visible on large files going straight to the CDN.
- No more waiting on transcodes: video processing moved to a dedicated worker in the background. You can keep working while clips transcode, and larger files no longer time out mid-upload.
- Mobile & tablet: full pass on the dashboard and model pages so they're finally usable on phones and tablets without pinching.
Member roles & bigger storage
- Roles in the UI: member and admin roles are now enforced everywhere. Non-admins no longer see buttons and settings they can't actually use.
- More storage by default: every organization gets a larger default storage quota — plenty of room before you need to think about it.
- Faster chat: unread counts in the sidebar no longer slow down on busy orgs with lots of messages.