@sudowrite/shared-chat

v0.7.2

Published

Cross-platform Tamagui chat UI shared between Sudowrite web and React Native

Readme

@sudowrite/shared-chat

Cross-platform Tamagui chat UI shared between Sudowrite web and React Native.

Published to the public npm registry as @sudowrite/shared-chat. (Version 0.1.0 was briefly published as @rebecca-sudowrite/shared-chat before the package moved to the @sudowrite org scope.)

Package layout

  • src/ — Tamagui components, provider, and shared Storybook stories
  • tamagui.config.ts — Sudowrite chat theme tokens
  • dist/ — Generated by npm run build; gitignored, published to npm

Build outputs:

  • Web (ESM/CJS): dist/esm, dist/cjs
  • React Native: dist/jsx via the react-native export condition in package.json

Development

npm install
npm run build                # tamagui-build → dist/ + types/
npm run type-check
npm run verify-native-export # validates package + native export conditions
npm run clean                # remove generated artifacts

Design tokens and themes

Sudokit exposes the exact Sudowrite themes sorbet, moonbeam, sepia, amber, forest, aurora-borealis, cauldron, and charcoal. The compatibility names light and dark resolve to Sorbet and Charcoal, respectively.

SharedChatProvider accepts the exact names (plus light and dark) and handles the scheme-aware Tamagui mapping:

import { SharedChatProvider } from "@sudowrite/shared-chat";

<SharedChatProvider theme="forest">{children}</SharedChatProvider>;

Consumers that use TamaguiProvider and the config directly must call normalizeTamaguiThemeName. The unprefixed exact config aliases are value-compatible, but Tamagui infers scheme, component-theme, and inverse-theme behavior from the internal light_ / dark_ names.

import { TamaguiProvider } from "@tamagui/core";
import config, {
  normalizeTamaguiThemeName,
} from "@sudowrite/shared-chat/config";

<TamaguiProvider
  config={config}
  defaultTheme={normalizeTamaguiThemeName("forest")}
>
  {children}
</TamaguiProvider>;

The generated web CJS and ESM entries target application bundlers. Importing those generated files directly in bare Node is not a supported package condition.

Both package entries expose:

  • Raw sudokitPrimitives and sudokitShadows.
  • exactSudowriteThemeNames, sharedChatThemeNames, sudokitThemes, and normalizeSharedChatTheme, with the ExactSudowriteThemeName, SharedChatThemeName, and SudokitTheme types.
  • The scheme-aware normalizeTamaguiThemeName resolver and its stable ResolvedTamaguiThemeName return type.
  • sudowriteTokens, sudokitFonts, and sudowriteChatConfig, with the SudowriteChatConfig type.

Consumers load the Inter, Quattro, OpenDyslexic, GT Super Text, GT Super Display, IBM Plex Mono, and MedievalSharp assets themselves. The token snapshot bundles no font binaries, background images, scrollbar CSS rules, CSS filters, or other visual artifacts. Its values are a portable snapshot of canonical Sudowrite Express CSS and conventions, not a runtime or build-time cross-repo dependency.

Web usage

npm install @sudowrite/shared-chat tamagui @tamagui/core @tamagui/config

Wrap chat surfaces with SharedChatProvider, then import presentational components:

import {
  SharedChatProvider,
  ConversationStarters,
  ChatMessageBubble,
} from "@sudowrite/shared-chat";

Pass icons={defaultChatIcons} (or individual icon components) for conversation starters and suggested actions. Paths match the Sudowrite web assets.

import {
  ConversationStarters,
  defaultChatIcons,
  PersonalizationIcon,
  RewriteIcon,
} from "@sudowrite/shared-chat";

React Native usage

Install peer dependencies in the mobile app:

npm install @sudowrite/shared-chat tamagui @tamagui/core react-native-svg

Import from the package using Metro's react-native export resolution:

import {
  SharedChatProvider,
  ConversationStarters,
  defaultChatIcons,
} from "@sudowrite/shared-chat";

Configure @tamagui/babel-plugin in the mobile app's babel.config.js with components: ["@sudowrite/shared-chat", "tamagui"].

Native builds resolve dist/jsx/index.native.js (react-native-svg icons). Web builds resolve the ESM/CJS entries (inline SVG via ChatIcons.web.tsx).

Storybook

Shared stories live in src/stories/ and ship in the npm tarball (files includes src), so consuming apps run them in their own Storybook setups:

  • Web (Sudowrite client): globs node_modules/@sudowrite/shared-chat/src/stories/** — run npm run storybook in client/ and open the SharedChat/ stories.
  • React Native (mobile app): bun run storybook:generate then bun run storybook:ios / storybook:android from mobile/app.

Render previews

render.yaml defines a static Storybook site (npm run build-storybookstorybook-static) with automatic PR previews. After the Blueprint is linked in the Sudowrite Render workspace, each PR gets its own Storybook URL.

Components

  • ConversationStarters / ConversationStartersContainer
  • SuggestedActionCard
  • ChatMessageBubble / ChatPanel / ChatMessagesList / ChatComposerShell layout shells
  • ChatComposerShell with editor / footer slots
  • ChatComposerModeSwitcher
  • ChatComposerSendStopButton
  • ComposerAttachmentStrip
  • WriteModeBadge
  • Icons: defaultChatIcons, PencilIcon, ChatIcon, SendIcon, StopIcon, CloseIcon, LightningIcon, RewriteIcon, DocumentIcon, PersonalizationIcon
  • Helpers: resolveDefaultConversationStarters, resolveImportConversationStarters

Pressable starter/action shells use Tamagui XStack/YStack (not unstyled Button) so they keep layout defaults on React Native, including inside absolutely positioned empty states.

Platform-specific concerns (TipTap composer, Zustand store, analytics, InlineSVG) remain in the consuming apps and wrap these shared primitives.

ChatPanel intentionally excludes a header slot so mobile/web can keep their own platform-specific headers. Shared shells cover the chat body: message viewport, floating overlay lane, and composer region.

Commits

Commit messages follow Conventional Commits (feat:, fix:, chore:, docs:, …) — they drive the version bump and the changelog. A husky commit-msg hook lints messages locally, and CI lints both PR commits and the PR title (the title becomes the squash-merge commit on main).

Releasing & publishing

Releases are automated with release-please:

  1. Merge conventional commits to main.
  2. release-please maintains a release PR that accumulates changes, updating CHANGELOG.md and the semver version (feat: → minor, fix: → patch, feat!:/BREAKING CHANGE: → major).
  3. Merging the release PR tags the release and publishes to npm using the NPM_TOKEN repo secret.

If a publish fails (or a release is cut by hand), the Publish to npm workflow can be re-run from the Actions tab.