feat: session sharing frontend (#76)

* feat: add session sharing types, API client, and utilities

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add SessionTimeline and ActionMenu reusable components

SessionTimeline extracts timeline/checklist rendering from SessionDetailPage
into a reusable component for both authenticated and public session views.
ActionMenu provides a dropdown action menu with keyboard/click-outside dismiss.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add ShareSessionModal and integrate into SessionDetailPage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add Share Progress popover to TreeNavigationPage

Replace the single "Copy for Ticket" button with a "Share Progress"
popover that offers three actions: Copy Progress Summary (existing PSA
export flow), Copy Share Link (auto-creates account-only share if
needed), and Manage Share Links (opens ShareSessionModal).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add public SharedSessionPage with tree preview

Add the public-facing shared session page at /share/:shareToken that
renders shared sessions without authentication. Includes error handling
for 401 (redirect to login), 403 (access denied), 404 (not found),
and 410 (expired). The page features a minimal header, session metadata,
SessionTimeline component, and a new SharedSessionTreePreview component
that renders the decision tree structure with the path taken highlighted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add My Shares management page with nav link

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address code review issues in session sharing

- Add useCallback for loadShares in ShareSessionModal (React hook deps)
- Use TreeStructure type instead of Record<string, unknown> for type safety
- Fix login redirect format to match LoginPage's expected state shape

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add focused tests for session sharing utilities and API

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve tree_structure type compatibility for shared session views

- Use TreeStructure & Record<string, unknown> intersection for JSONB flexibility
- Add explicit cast in SharedSessionTreePreview for recursive node rendering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add session sharing learnings to CLAUDE.md

Add gotchas #12 (TreeStructure vs Tree types) and #13 (login redirect
state format), note about npm run build strictness, and public route
pattern to Common Tasks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: procedural editor UX improvements

Add URL intake field type, fix variable name editing collapsing fields
(index-based keys/updates), auto-generate variable names by field type,
add section header as first-class step type, and simplify step editor
with "More Options" collapsible for advanced fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: allow section_header step type in validation, improve tag input

- Add 'section_header' to VALID_STEP_TYPES in backend validation so
  procedural flows with section headers can be published
- Replace procedural editor's inline tag input with TagInput component
  (supports autocomplete, Tab, comma, semicolon, and paste splitting)
- Add semicolon delimiter support to TagInput component

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add type-aware routing for procedural flows

Centralizes tree navigation routing via getTreeNavigatePath helper.
Fixes all pages to route procedural sessions to /flows/:id/navigate
instead of /trees/:id/navigate. Adds safety redirect in troubleshooting
navigator and resume support in procedural navigator.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove unused index prop from IntakeFieldEditor

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #76.
This commit is contained in:
chihlasm
2026-02-14 23:08:17 -05:00
committed by GitHub
parent 6b4304ab92
commit 57f429f33b
32 changed files with 2199 additions and 426 deletions

View File

@@ -1,4 +1,4 @@
import { Plus, GripVertical, Trash2, ChevronDown, CheckCircle2, AlertTriangle, Info, Zap, Shield } from 'lucide-react'
import { Plus, GripVertical, Trash2, ChevronDown, CheckCircle2, AlertTriangle, Info, Zap, Shield, SeparatorHorizontal } from 'lucide-react'
import type { StepContentType } from '@/types'
import { StepEditor } from './StepEditor'
import { useProceduralEditorStore } from '@/store/proceduralEditorStore'
@@ -18,11 +18,13 @@ export function StepList() {
expandedStepId,
setExpandedStepId,
addStep,
addSectionHeader,
removeStep,
updateStep,
} = useProceduralEditorStore()
const procedureSteps = steps.filter((s) => s.type === 'procedure_step')
let stepCounter = 0
return (
<div className="glass-card rounded-2xl p-4 sm:p-6">
@@ -34,19 +36,27 @@ export function StepList() {
({procedureSteps.length} step{procedureSteps.length !== 1 ? 's' : ''})
</span>
</div>
<button
onClick={() => addStep()}
className="flex items-center gap-1.5 rounded-md border border-white/10 px-3 py-1.5 text-sm text-white/60 hover:bg-white/10 hover:text-white"
>
<Plus className="h-3.5 w-3.5" />
Add Step
</button>
<div className="flex items-center gap-2">
<button
onClick={() => addSectionHeader()}
className="flex items-center gap-1.5 rounded-md border border-white/10 px-3 py-1.5 text-sm text-white/60 hover:bg-white/10 hover:text-white"
>
<SeparatorHorizontal className="h-3.5 w-3.5" />
Add Section
</button>
<button
onClick={() => addStep()}
className="flex items-center gap-1.5 rounded-md border border-white/10 px-3 py-1.5 text-sm text-white/60 hover:bg-white/10 hover:text-white"
>
<Plus className="h-3.5 w-3.5" />
Add Step
</button>
</div>
</div>
<div className="space-y-2">
{steps.map((step, index) => {
{steps.map((step) => {
if (step.type === 'procedure_end') {
// Render end step as a simple footer
return (
<div
key={step.id}
@@ -65,20 +75,57 @@ export function StepList() {
)
}
// Section header rendering
if (step.type === 'section_header') {
const isExpanded = expandedStepId === step.id
if (isExpanded) {
return (
<div key={step.id}>
<StepEditor
step={step}
stepNumber={0}
onUpdate={(updates) => updateStep(step.id, updates)}
onCollapse={() => setExpandedStepId(null)}
availableVariables={intakeForm}
/>
</div>
)
}
return (
<div
key={step.id}
className="group flex items-center gap-2 border-b border-white/[0.06] pb-1 pt-3"
>
<GripVertical className="h-4 w-4 shrink-0 cursor-grab text-white/20 group-hover:text-white/40" />
<span
className="min-w-0 flex-1 cursor-pointer text-xs font-semibold uppercase tracking-wider text-white/40 hover:text-white/60"
onClick={() => setExpandedStepId(step.id)}
>
{step.title || 'Untitled Section'}
</span>
<button
onClick={() => removeStep(step.id)}
className="shrink-0 rounded p-1 text-white/30 opacity-0 hover:bg-red-500/20 hover:text-red-400 group-hover:opacity-100"
>
<Trash2 className="h-3.5 w-3.5" />
</button>
</div>
)
}
// Regular procedure step
stepCounter++
const stepNumber = stepCounter
const isExpanded = expandedStepId === step.id
const contentType = step.content_type || 'action'
const config = contentTypeConfig[contentType]
const Icon = config.icon
const stepNumber = index + 1
if (isExpanded) {
return (
<div key={step.id}>
{step.section_header && (
<div className="mb-2 mt-4 border-b border-white/[0.06] pb-1 text-xs font-semibold uppercase tracking-wider text-white/40">
{step.section_header}
</div>
)}
<StepEditor
step={step}
stepNumber={stepNumber}
@@ -92,11 +139,6 @@ export function StepList() {
return (
<div key={step.id}>
{step.section_header && (
<div className="mb-2 mt-4 border-b border-white/[0.06] pb-1 text-xs font-semibold uppercase tracking-wider text-white/40">
{step.section_header}
</div>
)}
<div
className={cn(
'group flex items-center gap-2 rounded-xl border border-white/[0.06] px-3 py-2.5 transition-colors',