Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
5c5ceb8
style: increase type scale by 1px across all font size tokens
shiphrahx May 23, 2026
0967339
style: increase type scale by another 1px
shiphrahx May 23, 2026
f387069
fix(settings): show real user email from auth session
shiphrahx May 23, 2026
de57661
fix(settings): persist and load preferred name from user_profiles
shiphrahx May 23, 2026
9a5a4b6
fix(ai): rename edge function secret to AI_ENCRYPTION_KEY
shiphrahx May 23, 2026
283b78f
style(settings): improve text contrast in AI settings card
shiphrahx May 23, 2026
a3f1900
assets: update logo, remove unused banner
shiphrahx May 23, 2026
c3b1ee1
fix: bust logo cache with version query param
shiphrahx May 23, 2026
141e3d2
fix: use unoptimized prop on logo to bypass Next.js image cache
shiphrahx May 23, 2026
dbdb6af
style: increase sidebar logo size
shiphrahx May 23, 2026
58bfb84
style: increase vertical spacing around sidebar logo
shiphrahx May 23, 2026
f22ec97
adjusted header size
shiphrahx May 23, 2026
2b3cbe4
fix(people): make +N more signals expandable
shiphrahx May 23, 2026
0bd7d08
fix(signals): make new hire signal message list what's missing
shiphrahx May 23, 2026
a5137b8
fix(people): clarify signal count label and add tooltip explanation
shiphrahx May 23, 2026
d39c580
fix(people): show all signals by default, replace score with actionab…
shiphrahx May 23, 2026
7a061b0
fix(signals): suppress all person signals during first 7 days after hire
shiphrahx May 23, 2026
9e86def
style(competency): apply gradient to Add plan button
shiphrahx May 23, 2026
5059dfb
fix(competency): use caption font size on Add plan button
shiphrahx May 23, 2026
ecf2777
fix: enforce 13px minimum font size across all hardcoded values
shiphrahx May 23, 2026
f4f14aa
fix: enforce 13px minimum font size — charts, competency radar, landi…
shiphrahx May 23, 2026
9979c38
fix: replace all text-xs and sub-13px Tailwind font sizes with text-[…
shiphrahx May 23, 2026
1888dec
style: add utility CSS classes to globals.css for inline style migration
shiphrahx May 23, 2026
2c964f7
refactor(ui): remove inline styles from ai-button.tsx
shiphrahx May 23, 2026
6bcc236
refactor(ai-settings-card): remove inline styles
shiphrahx May 23, 2026
75db452
refactor(dashboard-layout,loading): remove inline styles
shiphrahx May 23, 2026
39781d5
refactor(sidebar): remove inline styles
shiphrahx May 23, 2026
2bd2626
refactor(dashboard widgets): remove inline styles
shiphrahx May 23, 2026
9d60e15
refactor(task-priority-chart): remove inline styles
shiphrahx May 23, 2026
0f2509f
refactor(people-table): remove inline styles
shiphrahx May 23, 2026
4593532
refactor(person-form-dialog,team-form-dialog): remove inline styles
shiphrahx May 23, 2026
365eede
refactor(teams-table,follow-up-draft-modal): remove inline styles
shiphrahx May 23, 2026
616f76e
refactor(follow-up-form,follow-up-list): remove inline styles
shiphrahx May 23, 2026
7e73bd7
refactor(dismiss-dialog,review-section): remove inline styles
shiphrahx May 23, 2026
575bf97
refactor(batch-evidence-import-modal): remove inline styles
shiphrahx May 23, 2026
b1e8d6c
refactor(evidence-section): remove inline styles
shiphrahx May 23, 2026
009a4d4
refactor(log-evidence-modal,sentiment-trend-chart): remove inline styles
shiphrahx May 23, 2026
7a3fe77
refactor(ui): remove inline styles from competency-section.tsx
shiphrahx May 23, 2026
53cd1c5
refactor(ui): remove inline styles from skills-matrix.tsx
shiphrahx May 23, 2026
5f3c71e
refactor(ui): remove inline styles from tasks components
shiphrahx May 23, 2026
da64035
refactor(ui): remove inline styles from meeting-form-dialog and data-…
shiphrahx May 23, 2026
c5715f8
refactor(ui): remove inline styles from simple page files
shiphrahx May 23, 2026
f45fcc3
refactor(ui): replace inline styles in follow-ups and evidence pages
shiphrahx May 23, 2026
97268bb
refactor(ui): replace inline styles in person detail page
shiphrahx May 23, 2026
93943ae
refactor(ui): replace inline styles in review prep page
shiphrahx May 23, 2026
0db5e25
refactor(ui): replace inline styles in career goals page
shiphrahx May 23, 2026
8eb7743
mid refactor
shiphrahx May 23, 2026
b1af608
refactor(ui): replace inline styles in framework and meetings pages
shiphrahx May 24, 2026
dcd8bf6
refactor(ui): replace inline styles in radar and weekly review pages
shiphrahx May 24, 2026
71bae6e
refactor(ui): replace inline styles in summary and team-health pages
shiphrahx May 24, 2026
3b19a28
refactor(ui): replace inline styles in privacy and terms pages
shiphrahx May 24, 2026
815fdcb
refactor(ui): replace static inline styles in sentiment-trend-chart a…
shiphrahx May 24, 2026
27f7b84
refactor(ui): replace static inline styles in competency-section
shiphrahx May 24, 2026
2a0bf36
refactor(ui): replace static inline styles in batch-evidence-import-m…
shiphrahx May 24, 2026
2b68fdf
refactor(ui): replace static inline styles in team-health-widget
shiphrahx May 24, 2026
9fee145
refactor(ui): replace static inline styles in evidence-section
shiphrahx May 24, 2026
e506558
refactor(ui): replace static inline styles in log-evidence-modal
shiphrahx May 24, 2026
e9c0808
refactor(ui): replace static inline styles in board-column and natura…
shiphrahx May 24, 2026
c1c78d6
refactor(ui): replace static inline styles in ai-settings-card
shiphrahx May 24, 2026
f5e7c84
refactor(ui): replace static inline styles in dismiss-dialog
shiphrahx May 24, 2026
aa1d3bd
refactor(ui): replace static inline styles in draggable-table-row
shiphrahx May 24, 2026
a94b574
refactor(ui): replace static inline styles in task-modal
shiphrahx May 24, 2026
e097bd9
fix(lint): move GoalsTable, FocusTable, MismatchBox outside CareerGoa…
shiphrahx May 24, 2026
156a83c
fix(test): fix 3 failing evidence-section tests with reliable selectors
shiphrahx May 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,578 changes: 387 additions & 1,191 deletions app/(dashboard)/career-goals/page.tsx

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions app/(dashboard)/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default async function DashboardPage() {
{/* Header */}
<div>
<h1>Dashboard</h1>
<p style={{ marginTop: "4px" }}>{label}</p>
<p className="mt-1">{label}</p>
</div>

{/* Weekly Review prompt */}
Expand All @@ -68,7 +68,7 @@ export default async function DashboardPage() {
<div className="bg-[#1c1c1c] border border-[#383838] rounded-xl p-5">
<div className="mb-4">
<h2>Priority Breakdown</h2>
<p style={{ marginTop: "2px" }}>Tasks by priority</p>
<p className="mt-0.5">Tasks by priority</p>
</div>
<TaskPriorityChart />
</div>
Expand Down
403 changes: 200 additions & 203 deletions app/(dashboard)/evidence/page.tsx

Large diffs are not rendered by default.

308 changes: 139 additions & 169 deletions app/(dashboard)/follow-ups/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ function formatDate(dateStr: string): string {
}

function StatusBadge({ fu }: { fu: FollowUp }) {
if (fu.status === 'completed') return <span style={{ fontSize: 'var(--text-caption)', fontWeight: 600, color: '#00f058', background: '#0d1f14', border: '1px solid #1a3a25', borderRadius: '4px', padding: '2px 6px' }}>Done</span>
if (fu.status === 'cancelled') return <span style={{ fontSize: 'var(--text-caption)', color: 'var(--text-3)', background: 'var(--surf-3)', border: '1px solid var(--border-2)', borderRadius: '4px', padding: '2px 6px' }}>Cancelled</span>
if (isOverdue(fu)) return <span style={{ fontSize: 'var(--text-caption)', fontWeight: 600, color: '#ff6b6b', background: '#2a0a0a', border: '1px solid #5a2020', borderRadius: '4px', padding: '2px 6px' }}>Overdue</span>
return <span style={{ fontSize: 'var(--text-caption)', color: 'var(--text-2)', background: 'var(--surf-3)', border: '1px solid var(--border-2)', borderRadius: '4px', padding: '2px 6px' }}>Open</span>
if (fu.status === 'completed') return <span className="fu-badge fu-badge--done">Done</span>
if (fu.status === 'cancelled') return <span className="fu-badge fu-badge--cancelled">Cancelled</span>
if (isOverdue(fu)) return <span className="fu-badge fu-badge--overdue">Overdue</span>
return <span className="fu-badge fu-badge--open">Open</span>
}

export default function FollowUpsPage() {
Expand Down Expand Up @@ -80,7 +80,6 @@ export default function FollowUpsPage() {
? Math.round(resolutionTimes.reduce((a, b) => a + b, 0) / resolutionTimes.length)
: null


return (
<>
<div className="page-topbar">
Expand All @@ -91,185 +90,156 @@ export default function FollowUpsPage() {
</div>
<div className="page-content">

{/* Stats bar */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '10px', marginBottom: '20px' }}>
{[
{ label: 'Open', value: openCount, color: 'var(--text-1)' },
{ label: 'Overdue', value: overdueCount, color: overdueCount > 0 ? '#ff6b6b' : 'var(--text-1)' },
{ label: 'Done this month', value: completedThisMonth, color: '#00f058' },
{ label: 'Avg resolution', value: avgResolution !== null ? `${avgResolution}d` : '—', color: 'var(--text-2)' },
].map(stat => (
<div key={stat.label} style={{ background: 'var(--surf)', border: '1px solid var(--border-1)', borderRadius: '6px', padding: '12px 14px' }}>
<div className="form-label">{stat.label}</div>
<div style={{ fontSize: 'var(--text-section)', fontWeight: 700, color: stat.color }}>{stat.value}</div>
</div>
))}
</div>

{/* View + filters */}
<div style={{ display: 'flex', gap: '8px', marginBottom: '16px', flexWrap: 'wrap', alignItems: 'center' }}>
{(['active', 'completed', 'all'] as ViewMode[]).map(v => (
<button
key={v}
onClick={() => setViewMode(v)}
style={{
background: viewMode === v ? 'var(--surf-3)' : 'var(--surf-2)',
border: `1px solid ${viewMode === v ? 'var(--border-3)' : 'var(--border-1)'}`,
borderRadius: '4px',
color: viewMode === v ? 'var(--text-1)' : 'var(--text-3)',
fontSize: 'var(--text-meta)',
padding: '5px 12px',
cursor: 'pointer',
fontFamily: 'var(--font-sans)',
textTransform: 'capitalize',
}}
>
{v}
</button>
))}

<select
value={personFilter}
onChange={e => setPersonFilter(e.target.value)}
style={{
background: 'var(--surf-2)',
border: '1px solid var(--border-1)',
borderRadius: '4px',
color: 'var(--text-2)',
fontSize: 'var(--text-meta)',
padding: '5px 10px',
cursor: 'pointer',
fontFamily: 'var(--font-sans)',
}}
>
<option value="all">All people</option>
{people.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
</select>

{viewMode === 'active' && (
<label style={{ display: 'flex', alignItems: 'center', gap: '6px', fontSize: 'var(--text-meta)', color: 'var(--text-2)', cursor: 'pointer' }}>
<input
type="checkbox"
checked={showOverdueOnly}
onChange={e => setShowOverdueOnly(e.target.checked)}
/>
Overdue only
</label>
)}
</div>

{/* Follow-up rows */}
{loading ? (
<p style={{ fontSize: 'var(--text-meta)', color: 'var(--text-3)' }}>Loading...</p>
) : visible.length === 0 ? (
<div style={{ padding: '32px 0', textAlign: 'center' }}>
<p style={{ color: 'var(--text-3)', fontSize: 'var(--text-meta)' }}>
{viewMode === 'active' ? 'No open follow-ups. Nice work.' : 'Nothing to show.'}
</p>
{/* Stats bar */}
<div className="fu-stats-grid">
{[
{ label: 'Open', value: openCount, color: 'var(--text-1)' },
{ label: 'Overdue', value: overdueCount, color: overdueCount > 0 ? '#ff6b6b' : 'var(--text-1)' },
{ label: 'Done this month', value: completedThisMonth, color: '#00f058' },
{ label: 'Avg resolution', value: avgResolution !== null ? `${avgResolution}d` : '—', color: 'var(--text-2)' },
].map(stat => (
<div key={stat.label} className="fu-stat-card">
<div className="form-label">{stat.label}</div>
<div className="fu-stat-value" style={{ color: stat.color }}>{stat.value}</div>
</div>
))}
</div>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: '2px' }}>
{/* Column headers */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 140px 120px 100px 120px', gap: '12px', padding: '6px 12px' }}>
<span className="col-header">Title</span>
<span className="col-header">Person</span>
<span className="col-header">Due / Age</span>
<span className="col-header">Status</span>
<span className="col-header">Source</span>
</div>

{visible.map(fu => (
<div
key={fu.id}
{/* View + filters */}
<div className="fu-filters-row">
{(['active', 'completed', 'all'] as ViewMode[]).map(v => (
<button
key={v}
onClick={() => setViewMode(v)}
className="fu-view-btn"
style={{
display: 'grid',
gridTemplateColumns: '1fr 140px 120px 100px 120px',
gap: '12px',
alignItems: 'center',
padding: '10px 12px',
background: isOverdue(fu) ? '#1a0a0a' : 'var(--surf)',
border: `1px solid ${isOverdue(fu) ? '#3a1515' : 'var(--border-1)'}`,
borderRadius: '5px',
background: viewMode === v ? 'var(--surf-3)' : 'var(--surf-2)',
border: `1px solid ${viewMode === v ? 'var(--border-3)' : 'var(--border-1)'}`,
color: viewMode === v ? 'var(--text-1)' : 'var(--text-3)',
}}
>
<div style={{ minWidth: 0 }}>
<div style={{ fontSize: 'var(--text-meta)', color: 'var(--text-1)', fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{fu.title}
{v}
</button>
))}

<select
value={personFilter}
onChange={e => setPersonFilter(e.target.value)}
className="fu-filter-select"
>
<option value="all">All people</option>
{people.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
</select>

{viewMode === 'active' && (
<label className="fu-overdue-label">
<input
type="checkbox"
checked={showOverdueOnly}
onChange={e => setShowOverdueOnly(e.target.checked)}
/>
Overdue only
</label>
)}
</div>

{/* Follow-up rows */}
{loading ? (
<p className="fu-loading">Loading...</p>
) : visible.length === 0 ? (
<div className="fu-empty">
<p className="fu-empty-text">
{viewMode === 'active' ? 'No open follow-ups. Nice work.' : 'Nothing to show.'}
</p>
</div>
) : (
<div className="fu-list">
{/* Column headers */}
<div className="fu-col-headers">
<span className="col-header">Title</span>
<span className="col-header">Person</span>
<span className="col-header">Due / Age</span>
<span className="col-header">Status</span>
<span className="col-header">Source</span>
</div>

{visible.map(fu => (
<div
key={fu.id}
className="fu-row"
style={{
background: isOverdue(fu) ? '#1a0a0a' : 'var(--surf)',
border: `1px solid ${isOverdue(fu) ? '#3a1515' : 'var(--border-1)'}`,
}}
>
<div className="fu-row-title">
<div className="fu-title-text">{fu.title}</div>
{fu.description && (
<div className="fu-desc-text">{fu.description}</div>
)}
</div>
{fu.description && (
<div style={{ fontSize: 'var(--text-caption)', color: 'var(--text-3)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', marginTop: '2px' }}>
{fu.description}
</div>
)}
</div>

<div>
{fu.personName ? (
<Link href={`/people/${fu.personId}`} style={{ fontSize: 'var(--text-meta)', color: 'var(--text-2)', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: '3px' }}>
{fu.personName} <ExternalLink style={{ width: '9px', height: '9px' }} />
</Link>
) : (
<span style={{ fontSize: 'var(--text-meta)', color: 'var(--text-3)' }}>—</span>
)}
</div>
<div>
{fu.personName ? (
<Link href={`/people/${fu.personId}`} className="fu-person-link">
{fu.personName} <ExternalLink />
</Link>
) : (
<span className="fu-person-none">—</span>
)}
</div>

<div style={{ fontSize: 'var(--text-meta)', color: 'var(--text-3)' }}>
{fu.dueDate ? formatDate(fu.dueDate) : `${ageDays(fu.createdAt)}d old`}
</div>
<div className="fu-date-cell">
{fu.dueDate ? formatDate(fu.dueDate) : `${ageDays(fu.createdAt)}d old`}
</div>

<div><StatusBadge fu={fu} /></div>
<div><StatusBadge fu={fu} /></div>

<div style={{ fontSize: 'var(--text-caption)', color: 'var(--text-3)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{fu.sourceName ?? (fu.sourceType ?? '—')}
<div className="fu-source-cell">
{fu.sourceName ?? (fu.sourceType ?? '—')}
</div>
</div>
</div>
))}
</div>
)}
))}
</div>
)}

{/* Person picker for new follow-up */}
{showPersonPicker && (
<div
style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.6)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }}
onClick={() => setShowPersonPicker(false)}
>
<div
style={{ background: 'var(--surf-2)', border: '1px solid var(--border-2)', borderRadius: '8px', padding: '20px', width: '320px', display: 'flex', flexDirection: 'column', gap: '12px' }}
onClick={e => e.stopPropagation()}
>
<div>
<span style={{ fontSize: 'var(--text-body)', fontWeight: 600, color: 'var(--text-1)' }}>Who is this follow-up for?</span>
<p style={{ fontSize: 'var(--text-meta)', color: 'var(--text-3)', marginTop: '4px' }}>Select a person to continue</p>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px', maxHeight: '300px', overflowY: 'auto' }}>
{loading ? (
<p style={{ fontSize: 'var(--text-meta)', color: 'var(--text-3)', padding: '8px 0' }}>Loading...</p>
) : people.length === 0 ? (
<p style={{ fontSize: 'var(--text-meta)', color: 'var(--text-3)', padding: '8px 0' }}>No active people found.</p>
) : people.map(p => (
<button
key={p.id}
onClick={() => { setAddingFor(p); setShowPersonPicker(false) }}
style={{ background: 'var(--surf-3)', border: '1px solid var(--border-2)', borderRadius: '4px', color: 'var(--text-1)', fontSize: 'var(--text-meta)', padding: '8px 12px', cursor: 'pointer', textAlign: 'left', fontFamily: 'var(--font-sans)' }}
>
{p.name} {p.role && <span style={{ color: 'var(--text-3)' }}>· {p.role}</span>}
</button>
))}
{/* Person picker for new follow-up */}
{showPersonPicker && (
<div className="fu-picker-overlay" onClick={() => setShowPersonPicker(false)}>
<div className="fu-picker-card" onClick={e => e.stopPropagation()}>
<div>
<span className="fu-picker-title">Who is this follow-up for?</span>
<p className="fu-picker-sub">Select a person to continue</p>
</div>
<div className="fu-picker-list">
{loading ? (
<p className="fu-picker-loading">Loading...</p>
) : people.length === 0 ? (
<p className="fu-picker-empty">No active people found.</p>
) : people.map(p => (
<button
key={p.id}
onClick={() => { setAddingFor(p); setShowPersonPicker(false) }}
className="fu-picker-btn"
>
{p.name} {p.role && <span className="fu-picker-btn-role">· {p.role}</span>}
</button>
))}
</div>
</div>
</div>
</div>
)}
)}

{addingFor && (
<FollowUpForm
personId={addingFor.id}
personName={addingFor.name}
sourceType="manual"
onSaved={() => { setAddingFor(null); load() }}
onCancel={() => setAddingFor(null)}
/>
)}
</div>
{addingFor && (
<FollowUpForm
personId={addingFor.id}
personName={addingFor.name}
sourceType="manual"
onSaved={() => { setAddingFor(null); load() }}
onCancel={() => setAddingFor(null)}
/>
)}
</div>
</>
)
}
Loading
Loading