Read-Only Mode Implementation¶
Overview¶
Read-only mode prevents users from modifying documents and annotations while still allowing viewing and navigation. This mode is triggered by insufficient permissions or explicit configuration.
When Read-Only Mode is Active¶
- No UPDATE Permission: User lacks CAN_UPDATE permission (corpus or document)
- Explicit Override: Parent component passes
readOnly={true}
- No Corpus Context: Document viewed outside corpus (limited features)
- Backend Lock: Document has
backendLock: true
Component Support Status¶
✅ Fully Implemented¶
Document Renderers¶
- PDF Component: Accepts
read_only
prop, prevents annotation creation - TxtAnnotatorWrapper: Accepts
readOnly
andallowInput
props
Annotation Components¶
- UnifiedLabelSelector: Disabled when
readOnly={true}
- SelectionLayer: Shows "Document is read-only" message
- AnnotationMenu: Shows only copy option (no Apply Label)
Floating Components¶
- FloatingDocumentInput: Hides chat mode button
- FloatingDocumentControls: Hides "Start New Analysis" button
- FloatingAnalysesPanel: Passes readOnly to AnalysisTraySelector
- FloatingExtractsPanel: Passes readOnly to ExtractTraySelector
- FloatingSummaryPreview: Display-only component
Content Components¶
- UnifiedContentFeed: Passes readOnly to all content items
- PostItNote: Changes cursor to default, hides edit indicator
- HighlightItem: Disables delete functionality
- RelationItem: Disables delete and edit functions
Other Components¶
- ChatTray: Starts fresh conversation, hides history
- UnifiedKnowledgeLayer: Hides "Edit Summary" button
- NoteEditor: Component hidden entirely
- NewNoteModal: Component hidden entirely
Components That Don't Need Changes¶
- ZoomControls: Zoom is inherently view-only
- SidebarControlBar: Navigation is view-only
- SearchResultCard: View-only by nature
Implementation Patterns¶
Pattern 1: Conditional Rendering¶
// Hide components that have no read-only use case
{!readOnly && (
<NoteEditor
noteId={editingNoteId}
onSave={handleSave}
/>
)}
Pattern 2: Prop Passing¶
// Pass readOnly to child components
<UnifiedContentFeed
readOnly={readOnly}
items={contentItems}
/>
Pattern 3: Conditional Handlers¶
// Disable handlers in read-only mode
<HighlightItem
annotation={annotation}
onDelete={readOnly ? undefined : handleDelete}
read_only={readOnly}
/>
Pattern 4: UI State Changes¶
// Change cursor and hover states
const PostItNote = styled.div<{ $readOnly?: boolean }>`
cursor: ${props => props.$readOnly ? 'default' : 'pointer'};
&:hover .edit-indicator {
display: ${props => props.$readOnly ? 'none' : 'block'};
}
`;
User Experience in Read-Only Mode¶
Visual Indicators¶
- Cursor Changes: Pointer cursor becomes default cursor
- Hidden Controls: Edit/delete buttons are hidden
- Disabled Inputs: Form inputs are disabled
- Status Messages: "Document is read-only" shown when attempting actions
Available Features¶
- ✅ View document content
- ✅ Navigate pages
- ✅ Zoom in/out
- ✅ Search within document
- ✅ View existing annotations
- ✅ Copy text
- ✅ View notes (cannot edit)
- ✅ Navigate between documents
Disabled Features¶
- ❌ Create new annotations
- ❌ Edit existing annotations
- ❌ Delete annotations
- ❌ Create/edit notes
- ❌ Start new analyses
- ❌ Modify summaries
- ❌ Change corpus settings
Testing Read-Only Mode¶
Unit Tests¶
describe('Read-Only Mode', () => {
it('should prevent annotation creation', () => {
render(<DocumentKnowledgeBase readOnly={true} />);
// Select text
selectText('sample text');
// Verify no annotation menu appears
expect(screen.queryByRole('menu')).not.toBeInTheDocument();
});
it('should hide edit buttons', () => {
render(<UnifiedKnowledgeLayer readOnly={true} />);
expect(screen.queryByText('Edit Summary')).not.toBeInTheDocument();
});
});
Integration Tests¶
it('should enforce read-only when user lacks permissions', () => {
const mockPermissions = ['CAN_READ']; // No UPDATE
render(
<MockedProvider mocks={[mockWithPermissions(mockPermissions)]}>
<DocumentKnowledgeBase documentId="123" corpusId="456" />
</MockedProvider>
);
// Verify read-only behavior
});
E2E Tests¶
test('read-only mode prevents modifications', async ({ page }) => {
// Login as read-only user
await loginAsReadOnlyUser(page);
// Navigate to document
await page.goto('/documents/123');
// Try to select text for annotation
await page.selectText('sample text');
// Verify no annotation menu
await expect(page.locator('.annotation-menu')).not.toBeVisible();
});
Migration Guide¶
For Existing Components¶
-
Add readOnly prop to interface:
interface MyComponentProps { // ... existing props readOnly?: boolean; }
-
Conditionally render edit controls:
{!readOnly && ( <Button onClick={handleEdit}>Edit</Button> )}
-
Disable interaction handlers:
const handleClick = readOnly ? undefined : () => { // Edit logic };
-
Update styles for read-only state:
cursor: ${props => props.readOnly ? 'default' : 'pointer'};
For New Components¶
- Always accept
readOnly
prop - Design with read-only mode in mind
- Provide clear visual feedback
- Test both modes thoroughly
Troubleshooting¶
Component not respecting read-only mode¶
- Verify component receives readOnly prop
- Check if handlers are conditionally disabled
- Ensure UI elements are hidden/disabled
Read-only mode too restrictive¶
- Identify view-only features being blocked
- Separate modification from viewing logic
- Allow navigation and viewing operations
Inconsistent read-only behavior¶
- Check permission flow from parent
- Verify all child components receive prop
- Ensure consistent prop naming (readOnly vs read_only)