diff --git a/apps/web/package.json b/apps/web/package.json
index bfe00e20..70b8b518 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -17,27 +17,27 @@
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@tailwindcss/postcss": "^4.3.3",
- "@tiptap/core": "^3.30.4",
- "@tiptap/extension-code-block-lowlight": "^3.30.4",
- "@tiptap/extension-details": "^3.30.4",
- "@tiptap/extension-document": "^3.30.4",
- "@tiptap/extension-highlight": "^3.30.4",
- "@tiptap/extension-image": "^3.30.4",
- "@tiptap/extension-link": "^3.30.4",
- "@tiptap/extension-paragraph": "^3.30.4",
- "@tiptap/extension-placeholder": "^3.30.4",
- "@tiptap/extension-table": "^3.30.4",
- "@tiptap/extension-table-cell": "^3.30.4",
- "@tiptap/extension-table-header": "^3.30.4",
- "@tiptap/extension-table-row": "^3.30.4",
- "@tiptap/extension-task-item": "^3.30.4",
- "@tiptap/extension-task-list": "^3.30.4",
- "@tiptap/extension-text": "^3.30.4",
- "@tiptap/extension-underline": "^3.30.4",
- "@tiptap/pm": "^3.30.4",
- "@tiptap/react": "^3.30.4",
- "@tiptap/starter-kit": "^3.30.4",
- "@tiptap/suggestion": "^3.30.4",
+ "@tiptap/core": "^3.30.5",
+ "@tiptap/extension-code-block-lowlight": "^3.30.5",
+ "@tiptap/extension-details": "^3.30.5",
+ "@tiptap/extension-document": "^3.30.5",
+ "@tiptap/extension-highlight": "^3.30.5",
+ "@tiptap/extension-image": "^3.30.5",
+ "@tiptap/extension-link": "^3.30.5",
+ "@tiptap/extension-paragraph": "^3.30.5",
+ "@tiptap/extension-placeholder": "^3.30.5",
+ "@tiptap/extension-table": "^3.30.5",
+ "@tiptap/extension-table-cell": "^3.30.5",
+ "@tiptap/extension-table-header": "^3.30.5",
+ "@tiptap/extension-table-row": "^3.30.5",
+ "@tiptap/extension-task-item": "^3.30.5",
+ "@tiptap/extension-task-list": "^3.30.5",
+ "@tiptap/extension-text": "^3.30.5",
+ "@tiptap/extension-underline": "^3.30.5",
+ "@tiptap/pm": "^3.30.5",
+ "@tiptap/react": "^3.30.5",
+ "@tiptap/starter-kit": "^3.30.5",
+ "@tiptap/suggestion": "^3.30.5",
"@types/react-resizable": "^4.0.0",
"@types/turndown": "^5.0.6",
"d3-drag": "^3.0.0",
@@ -47,7 +47,7 @@
"dompurify": "^3.4.13",
"lowlight": "^3.3.0",
"lucide-react": "^1.31.0",
- "next": "16.3.1",
+ "next": "16.3.3",
"postcss": "^8.5.26",
"react": "^19.2.8",
"react-dom": "^19.2.8",
@@ -75,7 +75,7 @@
"@types/react-grid-layout": "^1.3.6",
"@types/simple-peer": "^9.11.9",
"eslint": "^9.39.5",
- "eslint-config-next": "16.3.1",
+ "eslint-config-next": "16.3.3",
"typescript": "^6.0.3"
}
}
diff --git a/apps/web/src/app/(app)/settings/ai/page.tsx b/apps/web/src/app/(app)/settings/ai/page.tsx
index 3981cb3b..bdd09d3d 100644
--- a/apps/web/src/app/(app)/settings/ai/page.tsx
+++ b/apps/web/src/app/(app)/settings/ai/page.tsx
@@ -88,7 +88,7 @@ export default function AISettingsPage() {
AI configuration
Only owners and admins can configure AI providers for this workspace.
@@ -109,28 +109,28 @@ export default function AISettingsPage() {
if (!cfg) {
return (
-
{error || 'Failed to load.'}
+
{error || 'Failed to load.'}
);
}
return (
-
-
-
+
+
AI configuration for {org?.name ?? 'your workspace'}
-
-
+
+
Bring your own provider. Deft can use managed keys or local Ollama, and the core workspace keeps
working when no AI provider is configured. Keys are encrypted at rest and only used for this workspace.
{!cfg.has_provider && (
AI is currently off. Chat, tasks, wiki, calendar, and approvals still work; Defty and AI-assisted
@@ -147,7 +147,7 @@ export default function AISettingsPage() {
/>
Provider keys
@@ -169,12 +169,12 @@ export default function AISettingsPage() {
Model routing
-
+
Override which provider and model handle each kind of work. Leave a row at default to use Deft's tuned defaults.
@@ -186,12 +186,12 @@ export default function AISettingsPage() {
Embeddings
-
+
Powers semantic search across the wiki, tasks, and decisions. Falls back to keyword search when off.
@@ -199,12 +199,12 @@ export default function AISettingsPage() {
Voice transcription
-
+
Transcribes voice clips posted in chat. The default 'local' provider calls a self-hosted Whisper container — no audio leaves your infrastructure.
@@ -296,7 +296,7 @@ function ProviderCard({
-
{label}
+
{label}
{configured ? (
)}
-
+
{hint}
{configured && display && (
@@ -332,7 +332,7 @@ function ProviderCard({
{!editing && (
{ setEditing(true); setErr(''); setValue(''); }}
- className="text-[12px] font-medium px-3 py-1.5 rounded-md flex-shrink-0"
+ className="text-sm font-medium px-3 py-1.5 rounded-md flex-shrink-0"
style={{ background: 'var(--surface)', color: 'var(--foreground)', border: '1px solid var(--border)' }}
>
{configured ? 'Replace' : 'Add'}
@@ -344,7 +344,7 @@ function ProviderCard({
{err && (
{err}
@@ -358,7 +358,7 @@ function ProviderCard({
onChange={(e) => setValue(e.target.value)}
placeholder={placeholder}
autoFocus
- className="w-full h-9 px-3 pr-9 text-[13px] rounded-md outline-none font-mono"
+ className="w-full h-9 px-3 pr-9 text-sm rounded-md outline-none font-mono"
style={{ background: 'var(--surface)', border: '1px solid var(--border)', color: 'var(--foreground)' }}
/>
{!isOllama && (
@@ -376,15 +376,15 @@ function ProviderCard({
{saving ? '...' : 'Save'}
{ setEditing(false); setValue(''); setErr(''); }}
- className="h-9 px-3 text-[12px] font-medium rounded-md"
+ className="h-9 px-3 text-sm font-medium rounded-md"
style={{ background: 'var(--surface)', color: 'var(--foreground)', border: '1px solid var(--border)' }}
>
Cancel
@@ -394,7 +394,7 @@ function ProviderCard({
onClick={clear}
disabled={saving}
title="Clear stored value"
- className="h-9 px-3 flex items-center gap-1.5 text-[12px] font-medium rounded-md disabled:opacity-50"
+ className="h-9 px-3 flex items-center gap-1.5 text-sm font-medium rounded-md disabled:opacity-50"
style={{ background: 'var(--surface)', color: 'var(--error)', border: '1px solid var(--border)' }}
>
@@ -480,8 +480,8 @@ function ModelRouteCard({
-
{label}
-
+
{label}
+
{description}
@@ -492,7 +492,7 @@ function ModelRouteCard({
{ setEditing(true); setProvider(current?.provider ?? 'anthropic'); setModel(current?.model ?? ''); setErr(''); }}
- className="text-[12px] font-medium px-3 py-1.5 rounded-md"
+ className="text-sm font-medium px-3 py-1.5 rounded-md"
style={{ background: 'var(--surface)', color: 'var(--foreground)', border: '1px solid var(--border)' }}
>
{current ? 'Edit' : 'Set'}
@@ -502,7 +502,7 @@ function ModelRouteCard({
onClick={reset}
disabled={saving}
title="Reset to default"
- className="text-[12px] font-medium px-2 py-1.5 rounded-md disabled:opacity-50"
+ className="text-sm font-medium px-2 py-1.5 rounded-md disabled:opacity-50"
style={{ background: 'var(--surface)', color: 'var(--muted)', border: '1px solid var(--border)' }}
>
@@ -516,7 +516,7 @@ function ModelRouteCard({
{err && (
{err}
@@ -526,7 +526,7 @@ function ModelRouteCard({
setProvider(e.target.value as Provider)}
- className="h-9 px-2 text-[12px] rounded-md outline-none"
+ className="h-9 px-2 text-sm rounded-md outline-none"
style={{ background: 'var(--surface)', border: '1px solid var(--border)', color: 'var(--foreground)' }}
>
anthropic
@@ -539,20 +539,20 @@ function ModelRouteCard({
value={model}
onChange={(e) => setModel(e.target.value)}
placeholder="e.g. claude-sonnet-4-20250514"
- className="flex-1 min-w-[200px] h-9 px-3 text-[12px] rounded-md outline-none font-mono"
+ className="flex-1 min-w-[200px] h-9 px-3 text-sm rounded-md outline-none font-mono"
style={{ background: 'var(--surface)', border: '1px solid var(--border)', color: 'var(--foreground)' }}
/>
{saving ? '...' : 'Save'}
{ setEditing(false); setErr(''); }}
- className="h-9 px-3 text-[12px] font-medium rounded-md"
+ className="h-9 px-3 text-sm font-medium rounded-md"
style={{ background: 'var(--surface)', color: 'var(--foreground)', border: '1px solid var(--border)' }}
>
Cancel
@@ -615,7 +615,7 @@ function EmbedSection({ cfg, onSaved }: { cfg: AIConfig; onSaved: () => Promise<
-
Vector embeddings
+
Vector embeddings
{off ? (
Promise<
)}
-
+
Vectors are pinned to 1536 dimensions. Self-hosters can run an OpenAI-compatible server (LM Studio, vllm, llama.cpp) that emits matching dims.
@@ -649,13 +649,13 @@ function EmbedSection({ cfg, onSaved }: { cfg: AIConfig; onSaved: () => Promise<
{err && (
-
+
{err}
)}
-
+
Provider
Promise<
key={opt}
type="button"
onClick={() => setProvider(opt)}
- className="px-3 h-7 text-[12px] font-medium rounded transition-colors"
+ className="px-3 h-7 text-sm font-medium rounded transition-colors"
style={{
background: active ? 'var(--card-bg)' : 'transparent',
color: active ? 'var(--foreground)' : 'var(--muted)',
@@ -684,7 +684,7 @@ function EmbedSection({ cfg, onSaved }: { cfg: AIConfig; onSaved: () => Promise<
-
+
Model
Promise<
onChange={(e) => setModel(e.target.value)}
placeholder={DEFAULT_EMBED_MODEL}
disabled={off}
- className="w-full h-9 px-3 text-[13px] rounded-md outline-none font-mono disabled:opacity-50"
+ className="w-full h-9 px-3 text-sm rounded-md outline-none font-mono disabled:opacity-50"
style={{ background: 'var(--surface)', border: '1px solid var(--border)', color: 'var(--foreground)' }}
/>
-
Transcription provider
+
Transcription provider
currently: {cfg.transcription.effective}
-
+
Pick the engine used for voice clips in chat. The org override wins; clear it to fall back to the env default.
@@ -821,7 +821,7 @@ function TranscriptionSection({ cfg, onSaved }: { cfg: AIConfig; onSaved: () =>
{err && (
-
+
{err}
)}
@@ -848,7 +848,7 @@ function TranscriptionSection({ cfg, onSaved }: { cfg: AIConfig; onSaved: () =>
style={{ accentColor: 'var(--accent)' }}
/>
-
{opt.label}
+
{opt.label}
{opt.helper}
@@ -861,7 +861,7 @@ function TranscriptionSection({ cfg, onSaved }: { cfg: AIConfig; onSaved: () =>
type="button"
onClick={reset}
disabled={saving}
- className="text-[12px] inline-flex items-center gap-1 disabled:opacity-50"
+ className="text-sm inline-flex items-center gap-1 disabled:opacity-50"
style={{ color: 'var(--muted)' }}
>
@@ -870,8 +870,8 @@ function TranscriptionSection({ cfg, onSaved }: { cfg: AIConfig; onSaved: () =>
{saving ? '...' : 'Save'}
diff --git a/apps/web/src/app/(app)/settings/api-access/page.tsx b/apps/web/src/app/(app)/settings/api-access/page.tsx
index 74d674e0..787f23b4 100644
--- a/apps/web/src/app/(app)/settings/api-access/page.tsx
+++ b/apps/web/src/app/(app)/settings/api-access/page.tsx
@@ -139,7 +139,7 @@ export default function ApiAccessPage() {
return (
-
+
setShowCreate(!showCreate)}
- className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[12px] font-medium"
+ className="flex min-h-10 shrink-0 items-center gap-1.5 whitespace-nowrap px-3 py-1.5 rounded-lg text-[12px] font-medium"
style={{
background: 'var(--accent)',
color: 'white',
diff --git a/apps/web/src/app/(app)/settings/calendar/page.tsx b/apps/web/src/app/(app)/settings/calendar/page.tsx
index eef715f5..476be0da 100644
--- a/apps/web/src/app/(app)/settings/calendar/page.tsx
+++ b/apps/web/src/app/(app)/settings/calendar/page.tsx
@@ -88,15 +88,15 @@ export default function CalendarSettingsPage() {
const [section, setSection] = useState<'connected' | 'share'>('connected');
return (
-
-
-
+
+
Calendar connections
-
-
+
+
Connect calendars with ICS links: show Deft work in your calendar, and read external calendar events into Deft without OAuth.
@@ -164,12 +164,12 @@ function OutboundSection() {
return (
Show Deft in your calendar
-
+
Paste this URL into your calendar app to see your Deft tasks alongside your other events. Your calendar app re-fetches automatically — no OAuth, no plugin.
@@ -185,10 +185,10 @@ function OutboundSection() {
-
+
Your personal Deft feed URL
-
+
Tasks with due dates and synced events for the next 12 months.
@@ -197,7 +197,7 @@ function OutboundSection() {
{error && (
{error}
@@ -205,7 +205,7 @@ function OutboundSection() {
)}
{loading ? (
-
+
Loading…
) : feedUrl ? (
@@ -215,13 +215,13 @@ function OutboundSection() {
readOnly
value={feedUrl}
onFocus={(e) => e.currentTarget.select()}
- className="flex-1 h-9 px-3 text-[12px] rounded-md outline-none font-mono"
+ className="flex-1 h-9 px-3 text-sm rounded-md outline-none font-mono"
style={{ background: 'var(--surface)', border: '1px solid var(--border)', color: 'var(--foreground)' }}
/>
setHowToOpen((v) => !v)}
- className="mt-3 inline-flex items-center gap-1 text-[12px]"
+ className="mt-3 inline-flex items-center gap-1 text-sm"
style={{ color: 'var(--muted)' }}
>
{howToOpen ? : }
@@ -247,7 +247,7 @@ function OutboundSection() {
{howToOpen && (
-
+
Apple Calendar. {' '}
File → New Calendar Subscription → paste the URL.
@@ -268,7 +268,7 @@ function OutboundSection() {
{confirmRegen ? (
@@ -279,7 +279,7 @@ function OutboundSection() {
type="button"
onClick={() => setConfirmRegen(false)}
disabled={regenerating}
- className="h-8 px-3 text-[12px] font-medium rounded-md"
+ className="h-8 px-3 text-sm font-medium rounded-md"
style={{ background: 'var(--surface)', color: 'var(--foreground)', border: '1px solid var(--border)' }}
>
Cancel
@@ -288,7 +288,7 @@ function OutboundSection() {
type="button"
onClick={regenerate}
disabled={regenerating}
- className="h-8 px-3 flex items-center gap-1.5 text-[12px] font-medium rounded-md disabled:opacity-50"
+ className="h-8 px-3 flex items-center gap-1.5 text-sm font-medium rounded-md disabled:opacity-50"
style={{ background: 'var(--error)', color: 'white' }}
>
@@ -301,7 +301,7 @@ function OutboundSection() {
type="button"
onClick={() => setConfirmRegen(true)}
disabled={!feedUrl}
- className="inline-flex items-center gap-1.5 text-[12px] disabled:opacity-50"
+ className="inline-flex items-center gap-1.5 text-sm disabled:opacity-50"
style={{ color: 'var(--muted)' }}
>
@@ -336,7 +336,7 @@ function InboundSection() {
Connect external calendars to Deft
@@ -344,15 +344,15 @@ function InboundSection() {
{!showAdd && (
setShowAdd(true)}
- className="flex items-center gap-1 px-3 py-1 rounded-md text-[12px] font-medium"
- style={{ background: 'var(--accent)', color: 'white' }}
+ className="flex items-center gap-1 px-3 py-1 rounded-md text-sm font-medium"
+ style={{ background: 'var(--settings-action)', color: 'white' }}
>
Add
)}
-
+
Paste a secret ICS feed URL from Google, iCloud, or Outlook to read those events into Deft. The agent uses them for context — nothing's sent back.
@@ -369,14 +369,14 @@ function InboundSection() {
{loading ? (
Loading subscriptions…
) : subs.length === 0 ? (
No inbound calendar subscriptions yet. Add one above to start ingesting events.
@@ -396,7 +396,7 @@ function InboundSection() {
setWhereOpen((v) => !v)}
- className="inline-flex items-center gap-1 text-[12px]"
+ className="inline-flex items-center gap-1 text-sm"
style={{ color: 'var(--muted)' }}
>
{whereOpen ? : }
@@ -404,7 +404,7 @@ function InboundSection() {
{whereOpen && (
-
+
Google Calendar. {' '}
Settings → Settings for my calendars → pick the calendar → Integrate calendar → "Secret address in iCal format".
@@ -491,12 +491,12 @@ function AddSubscriptionForm({
style={{ background: 'var(--card-bg)', border: '1px solid var(--border)' }}
>
{err && (
-
+
{err}
)}
-
+
Calendar provider
@@ -516,12 +516,12 @@ function AddSubscriptionForm({
))}
-
+
{selectedProvider.hint}
-
+
ICS URL
@@ -539,10 +539,10 @@ function AddSubscriptionForm({
-
+
{preview.calendar_name || 'Calendar feed'}
-
+
Found {preview.event_count} event{preview.event_count === 1 ? '' : 's'}.
@@ -553,7 +553,7 @@ function AddSubscriptionForm({
{preview.upcoming.length > 0 && (
{preview.upcoming.map((event) => (
-
+
{event.title}
{formatDateTime(event.starts_at)}
@@ -564,7 +564,7 @@ function AddSubscriptionForm({
)}
-
+
Label (optional)
setLabel(e.target.value)}
placeholder="Personal calendar"
maxLength={120}
- className="w-full h-9 px-3 text-[13px] rounded-md outline-none"
+ className="w-full h-9 px-3 text-sm rounded-md outline-none"
style={{ background: 'var(--surface)', border: '1px solid var(--border)', color: 'var(--foreground)' }}
/>
-
+
Sync every
setInterval(Number(e.target.value))}
- className="h-9 px-2 text-[13px] rounded-md outline-none"
+ className="h-9 px-2 text-sm rounded-md outline-none"
style={{ background: 'var(--surface)', border: '1px solid var(--border)', color: 'var(--foreground)' }}
>
{INTERVAL_OPTIONS.map((o) => (
@@ -600,7 +600,7 @@ function AddSubscriptionForm({
type="button"
onClick={onCancel}
disabled={submitting}
- className="h-9 px-3 text-[12px] font-medium rounded-md"
+ className="h-9 px-3 text-sm font-medium rounded-md"
style={{ background: 'var(--surface)', color: 'var(--foreground)', border: '1px solid var(--border)' }}
>
Cancel
@@ -609,7 +609,7 @@ function AddSubscriptionForm({
type="button"
onClick={previewFeed}
disabled={previewing || submitting || !url.trim()}
- className="h-9 px-3 inline-flex items-center gap-1.5 text-[12px] font-medium rounded-md disabled:opacity-50"
+ className="h-9 px-3 inline-flex items-center gap-1.5 text-sm font-medium rounded-md disabled:opacity-50"
style={{ background: 'var(--surface)', color: 'var(--foreground)', border: '1px solid var(--border)' }}
>
@@ -618,8 +618,8 @@ function AddSubscriptionForm({
{submitting ? 'Adding…' : 'Add subscription'}
@@ -698,7 +698,7 @@ function SubscriptionRow({
-
+
{displayLabel}
{sub.label && (
@@ -764,7 +764,7 @@ function SubscriptionRow({
onClick={sync}
disabled={syncing}
title="Sync now"
- className="h-8 px-2 inline-flex items-center gap-1 text-[12px] font-medium rounded-md disabled:opacity-50"
+ className="h-8 px-2 inline-flex items-center gap-1 text-sm font-medium rounded-md disabled:opacity-50"
style={{ background: 'var(--surface)', color: 'var(--foreground)', border: '1px solid var(--border)' }}
>
diff --git a/apps/web/src/app/(app)/settings/layout.tsx b/apps/web/src/app/(app)/settings/layout.tsx
index deda039e..b4fde587 100644
--- a/apps/web/src/app/(app)/settings/layout.tsx
+++ b/apps/web/src/app/(app)/settings/layout.tsx
@@ -1,5 +1,7 @@
+import styles from './settings.module.css';
+
export const metadata = { title: 'Settings' };
export default function SettingsLayout({ children }: { children: React.ReactNode }) {
- return children;
+ return
{children}
;
}
diff --git a/apps/web/src/app/(app)/settings/library/page.tsx b/apps/web/src/app/(app)/settings/library/page.tsx
index 8fbe1ed9..3cf49056 100644
--- a/apps/web/src/app/(app)/settings/library/page.tsx
+++ b/apps/web/src/app/(app)/settings/library/page.tsx
@@ -602,7 +602,6 @@ export default function LibraryPage() {
{/* Skills tab */}
diff --git a/apps/web/src/app/(app)/settings/profile/page.tsx b/apps/web/src/app/(app)/settings/profile/page.tsx
index 00d47edb..bf800fdd 100644
--- a/apps/web/src/app/(app)/settings/profile/page.tsx
+++ b/apps/web/src/app/(app)/settings/profile/page.tsx
@@ -251,13 +251,12 @@ export default function ProfileSettingsPage() {
return (
-
+
-
Account
-
- My profile
+
+ Profile
-
+
Shape how teammates understand your role, availability, and collaboration preferences.
@@ -270,7 +269,7 @@ export default function ProfileSettingsPage() {
onChange={setSection}
/>