This document describes the contributions feature that enables authenticated participants to add verbatim contributions with optional tone metadata and view the ordered conversation timeline. The system automatically generates a human-readable slug after the first contribution if one does not exist.
- Path:
conversations/{conversationId} - Fields:
id(string): Unique identifiertitle(string): Conversation titleslug(string | null): Human-readable URL slugownerUid(string): Firebase Auth UID of ownercreatedAt(Timestamp): Creation timestampupdatedAt(Timestamp): Last update timestamp
- Path:
conversations/{conversationId}/contributions/{contributionId} - Fields:
id(string): Unique identifiercontent(string): The verbatim contribution text (1-5000 characters)tone(enum | null): One of: "neutral", "positive", "negative", "curious", "assertive"authorUid(string): Firebase Auth UID of authorauthorDisplayName(string): Display name of authorcreatedAt(Timestamp): Creation timestamp (immutable)
Firestore security rules are defined in firestore.rules:
-
Conversations:
- Read: Only conversation owner
- Update: Only owner, and only for
slugandupdatedAtfields
-
Contributions:
- Read: Only conversation owner
- Create: Only conversation owner with valid data
- Update/Delete: Denied (immutable after creation)
-
Validation:
- Content must be non-empty string (1-5000 characters)
- Tone must be null or one of the allowed values
- Author UID must match authenticated user
- Location:
src/react-app/components/ContributionList.tsx - Displays contributions ordered by
createdAtascending - Shows author, timestamp, content, and tone badge
- Handles loading and empty states
- Location:
src/react-app/components/ContributionComposer.tsx - Textarea input for contribution content
- Tone selector with chip-style buttons
- Client-side validation using Zod schema
- Disabled state for unauthenticated users
- Stores verbatim text (no trimming or normalization)
- Location:
src/react-app/hooks/useAuth.ts - Returns current authenticated user and loading state
- Location:
src/react-app/hooks/useConversation.ts - Fetches conversation data by ID
- Provides
updateConversationSlugfunction
- Location:
src/react-app/hooks/useContributions.ts - Real-time stream of contributions ordered by creation time
- Provides
createContributionfunction
- Location:
src/react-app/utils/slugify.ts - Functions:
slugify(text): Converts text to URL-safe slugextractFirstSentence(text): Extracts first sentence or first 10 wordsgenerateUniqueSlug(baseText, conversationId): Creates unique slug with collision detection
Zod schemas defined in src/react-app/schemas/contribution.ts:
- Content: 1-5000 characters
- Tone: null or one of the allowed enum values
When the first contribution is created:
- Check if conversation has a slug
- If not, generate slug from:
- Conversation title (if available), OR
- First sentence of contribution content
- Verify uniqueness via Firestore query
- Append numeric suffix if collision detected
- Update conversation document with slug and new
updatedAt
// In ConversationWorkspacePage
const { user } = useAuth();
const { conversation } = useConversation(conversationId);
const { contributions } = useContributions(conversationId);
await createContribution({
conversationId,
content: "This is my contribution",
tone: "positive",
authorUid: user.uid,
authorDisplayName: user.displayName || "Anonymous",
conversationTitle: conversation?.title,
currentSlug: conversation?.slug,
});Required Firebase configuration in .env:
VITE_FIREBASE_API_KEY=your-api-key
VITE_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=your-project-id
VITE_FIREBASE_STORAGE_BUCKET=your-project.appspot.com
VITE_FIREBASE_MESSAGING_SENDER_ID=your-sender-id
VITE_FIREBASE_APP_ID=your-app-id
To fully test this feature:
- Set up Firebase project with authentication enabled
- Create test conversations with owner UIDs
- Test contribution creation with various tones
- Verify slug generation on first contribution
- Test uniqueness by creating conversations with similar titles
- Verify contributions cannot be edited or deleted
- Test validation edge cases (empty content, oversized content)
✅ Owners can add contributions with verbatim storage ✅ Each contribution appears immediately with original text and tone label ✅ Contributions persist in Firestore and cannot be edited/deleted ✅ First contribution triggers slug auto-generation ✅ Slug is visible in workspace header ✅ Security rules reject invalid content and non-create mutations ✅ Composer hidden/disabled for unauthenticated users ✅ Contributions ordered by createdAt ascending ✅ Empty state shown when no contributions exist ✅ All build and type checks pass