| author | |
| committer | |
| log | 466d8ee5b0a003b3f735dd25e21273539b515316 |
| tree | 7eac4914d8a5fdc0506875d1504bd3466afa7a75 |
| parent | 11f40556de8a37c7f16486c23fb6bce058275557 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: Julius Marminge <jmarminge@gmail.com>11 files changed, 308 insertions(+), 33 deletions(-)
.plans/19-remote-endpoints-hosted-static.md-1| ... | @@ -347,4 +347,3 @@ Each implementation PR should run: | ... | @@ -347,4 +347,3 @@ Each implementation PR should run: |
| 347 | - `bun typecheck` | 347 | - `bun typecheck` |
| 348 | - focused tests for changed backend/web behavior | 348 | - focused tests for changed backend/web behavior |
| 349 | - backend tests for any server-side endpoint discovery or auth changes using `bun run test`, never `bun test` | 349 | - backend tests for any server-side endpoint discovery or auth changes using `bun run test`, never `bun test` |
| 350 |
apps/desktop/src/clientPersistence.test.ts+1| ... | @@ -63,6 +63,7 @@ const clientSettings: ClientSettings = { | ... | @@ -63,6 +63,7 @@ const clientSettings: ClientSettings = { |
| 63 | }, | 63 | }, |
| 64 | sidebarProjectSortOrder: "manual", | 64 | sidebarProjectSortOrder: "manual", |
| 65 | sidebarThreadSortOrder: "created_at", | 65 | sidebarThreadSortOrder: "created_at", |
| 66 | sidebarThreadPreviewCount: 6, | ||
| 66 | timestampFormat: "24-hour", | 67 | timestampFormat: "24-hour", |
| 67 | }; | 68 | }; |
| 68 | 69 |
apps/web/package.json+1-1| ... | @@ -13,7 +13,7 @@ | ... | @@ -13,7 +13,7 @@ |
| 13 | "test:browser:install": "playwright install --with-deps chromium" | 13 | "test:browser:install": "playwright install --with-deps chromium" |
| 14 | }, | 14 | }, |
| 15 | "dependencies": { | 15 | "dependencies": { |
| 16 | "@base-ui/react": "^1.2.0", | 16 | "@base-ui/react": "^1.4.1", |
| 17 | "@dnd-kit/core": "^6.3.1", | 17 | "@dnd-kit/core": "^6.3.1", |
| 18 | "@dnd-kit/modifiers": "^9.0.0", | 18 | "@dnd-kit/modifiers": "^9.0.0", |
| 19 | "@dnd-kit/sortable": "^10.0.0", | 19 | "@dnd-kit/sortable": "^10.0.0", |
apps/web/src/components/Sidebar.tsx+94-7| ... | @@ -54,7 +54,10 @@ import { | ... | @@ -54,7 +54,10 @@ import { |
| 54 | } from "@t3tools/client-runtime"; | 54 | } from "@t3tools/client-runtime"; |
| 55 | import { Link, useLocation, useNavigate, useParams, useRouter } from "@tanstack/react-router"; | 55 | import { Link, useLocation, useNavigate, useParams, useRouter } from "@tanstack/react-router"; |
| 56 | import { | 56 | import { |
| 57 | MAX_SIDEBAR_THREAD_PREVIEW_COUNT, | ||
| 58 | MIN_SIDEBAR_THREAD_PREVIEW_COUNT, | ||
| 57 | type SidebarProjectSortOrder, | 59 | type SidebarProjectSortOrder, |
| 60 | type SidebarThreadPreviewCount, | ||
| 58 | type SidebarThreadSortOrder, | 61 | type SidebarThreadSortOrder, |
| 59 | } from "@t3tools/contracts/settings"; | 62 | } from "@t3tools/contracts/settings"; |
| 60 | import { usePrimaryEnvironmentId } from "../environments/primary"; | 63 | import { usePrimaryEnvironmentId } from "../environments/primary"; |
| ... | @@ -128,6 +131,13 @@ import { | ... | @@ -128,6 +131,13 @@ import { |
| 128 | MenuSeparator, | 131 | MenuSeparator, |
| 129 | MenuTrigger, | 132 | MenuTrigger, |
| 130 | } from "./ui/menu"; | 133 | } from "./ui/menu"; |
| 134 | import { | ||
| 135 | NumberField, | ||
| 136 | NumberFieldDecrement, | ||
| 137 | NumberFieldGroup, | ||
| 138 | NumberFieldIncrement, | ||
| 139 | NumberFieldInput, | ||
| 140 | } from "./ui/number-field"; | ||
| 131 | import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "./ui/select"; | 141 | import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "./ui/select"; |
| 132 | import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; | 142 | import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; |
| 133 | import { | 143 | import { |
| ... | @@ -186,7 +196,6 @@ import { | ... | @@ -186,7 +196,6 @@ import { |
| 186 | type SidebarProjectSnapshot, | 196 | type SidebarProjectSnapshot, |
| 187 | } from "../sidebarProjectGrouping"; | 197 | } from "../sidebarProjectGrouping"; |
| 188 | import { SidebarProviderUpdatePill } from "./sidebar/SidebarProviderUpdatePill"; | 198 | import { SidebarProviderUpdatePill } from "./sidebar/SidebarProviderUpdatePill"; |
| 189 | const THREAD_PREVIEW_LIMIT = 6; | ||
| 190 | const SIDEBAR_SORT_LABELS: Record<SidebarProjectSortOrder, string> = { | 199 | const SIDEBAR_SORT_LABELS: Record<SidebarProjectSortOrder, string> = { |
| 191 | updated_at: "Last user message", | 200 | updated_at: "Last user message", |
| 192 | created_at: "Created at", | 201 | created_at: "Created at", |
| ... | @@ -207,6 +216,13 @@ const PROJECT_GROUPING_MODE_LABELS: Record<SidebarProjectGroupingMode, string> = | ... | @@ -207,6 +216,13 @@ const PROJECT_GROUPING_MODE_LABELS: Record<SidebarProjectGroupingMode, string> = |
| 207 | separate: "Keep separate", | 216 | separate: "Keep separate", |
| 208 | }; | 217 | }; |
| 209 | 218 | ||
| 219 | function clampSidebarThreadPreviewCount(value: number): SidebarThreadPreviewCount { | ||
| 220 | return Math.min( | ||
| 221 | MAX_SIDEBAR_THREAD_PREVIEW_COUNT, | ||
| 222 | Math.max(MIN_SIDEBAR_THREAD_PREVIEW_COUNT, value), | ||
| 223 | ) as SidebarThreadPreviewCount; | ||
| 224 | } | ||
| 225 | |||
| 210 | function formatProjectMemberActionLabel( | 226 | function formatProjectMemberActionLabel( |
| 211 | member: SidebarProjectGroupMember, | 227 | member: SidebarProjectGroupMember, |
| 212 | groupedProjectCount: number, | 228 | groupedProjectCount: number, |
| ... | @@ -936,6 +952,9 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec | ... | @@ -936,6 +952,9 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 936 | sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides, | 952 | sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides, |
| 937 | })); | 953 | })); |
| 938 | const { updateSettings } = useUpdateSettings(); | 954 | const { updateSettings } = useUpdateSettings(); |
| 955 | const sidebarThreadPreviewCount = useSettings<SidebarThreadPreviewCount>( | ||
| 956 | (settings) => settings.sidebarThreadPreviewCount, | ||
| 957 | ); | ||
| 939 | const router = useRouter(); | 958 | const router = useRouter(); |
| 940 | const { isMobile, setOpenMobile } = useSidebar(); | 959 | const { isMobile, setOpenMobile } = useSidebar(); |
| 941 | const markThreadUnread = useUiStateStore((state) => state.markThreadUnread); | 960 | const markThreadUnread = useUiStateStore((state) => state.markThreadUnread); |
| ... | @@ -1159,11 +1178,11 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec | ... | @@ -1159,11 +1178,11 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 1159 | }, | 1178 | }, |
| 1160 | }); | 1179 | }); |
| 1161 | }; | 1180 | }; |
| 1162 | const hasOverflowingThreads = visibleProjectThreads.length > THREAD_PREVIEW_LIMIT; | 1181 | const hasOverflowingThreads = visibleProjectThreads.length > sidebarThreadPreviewCount; |
| 1163 | const previewThreads = | 1182 | const previewThreads = |
| 1164 | isThreadListExpanded || !hasOverflowingThreads | 1183 | isThreadListExpanded || !hasOverflowingThreads |
| 1165 | ? visibleProjectThreads | 1184 | ? visibleProjectThreads |
| 1166 | : visibleProjectThreads.slice(0, THREAD_PREVIEW_LIMIT); | 1185 | : visibleProjectThreads.slice(0, sidebarThreadPreviewCount); |
| 1167 | const visibleThreadKeys = new Set( | 1186 | const visibleThreadKeys = new Set( |
| 1168 | [...previewThreads, ...(pinnedCollapsedThread ? [pinnedCollapsedThread] : [])].map((thread) => | 1187 | [...previewThreads, ...(pinnedCollapsedThread ? [pinnedCollapsedThread] : [])].map((thread) => |
| 1169 | scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)), | 1188 | scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)), |
| ... | @@ -1192,6 +1211,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec | ... | @@ -1192,6 +1211,7 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec |
| 1192 | pinnedCollapsedThread, | 1211 | pinnedCollapsedThread, |
| 1193 | projectExpanded, | 1212 | projectExpanded, |
| 1194 | projectThreads, | 1213 | projectThreads, |
| 1214 | sidebarThreadPreviewCount, | ||
| 1195 | threadLastVisitedAts, | 1215 | threadLastVisitedAts, |
| 1196 | visibleProjectThreads, | 1216 | visibleProjectThreads, |
| 1197 | ]); | 1217 | ]); |
| ... | @@ -2253,17 +2273,35 @@ function ProjectSortMenu({ | ... | @@ -2253,17 +2273,35 @@ function ProjectSortMenu({ |
| 2253 | projectSortOrder, | 2273 | projectSortOrder, |
| 2254 | threadSortOrder, | 2274 | threadSortOrder, |
| 2255 | projectGroupingMode, | 2275 | projectGroupingMode, |
| 2276 | threadPreviewCount, | ||
| 2256 | onProjectSortOrderChange, | 2277 | onProjectSortOrderChange, |
| 2257 | onThreadSortOrderChange, | 2278 | onThreadSortOrderChange, |
| 2258 | onProjectGroupingModeChange, | 2279 | onProjectGroupingModeChange, |
| 2280 | onThreadPreviewCountChange, | ||
| 2259 | }: { | 2281 | }: { |
| 2260 | projectSortOrder: SidebarProjectSortOrder; | 2282 | projectSortOrder: SidebarProjectSortOrder; |
| 2261 | threadSortOrder: SidebarThreadSortOrder; | 2283 | threadSortOrder: SidebarThreadSortOrder; |
| 2262 | projectGroupingMode: SidebarProjectGroupingMode; | 2284 | projectGroupingMode: SidebarProjectGroupingMode; |
| 2285 | threadPreviewCount: SidebarThreadPreviewCount; | ||
| 2263 | onProjectSortOrderChange: (sortOrder: SidebarProjectSortOrder) => void; | 2286 | onProjectSortOrderChange: (sortOrder: SidebarProjectSortOrder) => void; |
| 2264 | onThreadSortOrderChange: (sortOrder: SidebarThreadSortOrder) => void; | 2287 | onThreadSortOrderChange: (sortOrder: SidebarThreadSortOrder) => void; |
| 2265 | onProjectGroupingModeChange: (mode: SidebarProjectGroupingMode) => void; | 2288 | onProjectGroupingModeChange: (mode: SidebarProjectGroupingMode) => void; |
| 2289 | onThreadPreviewCountChange: (count: SidebarThreadPreviewCount) => void; | ||
| 2266 | }) { | 2290 | }) { |
| 2291 | const handleThreadPreviewCountChange = useCallback( | ||
| 2292 | (nextValue: number | null) => { | ||
| 2293 | if (nextValue === null) { | ||
| 2294 | return; | ||
| 2295 | } | ||
| 2296 | |||
| 2297 | const clampedValue = clampSidebarThreadPreviewCount(nextValue); | ||
| 2298 | if (clampedValue !== threadPreviewCount) { | ||
| 2299 | onThreadPreviewCountChange(clampedValue); | ||
| 2300 | } | ||
| 2301 | }, | ||
| 2302 | [onThreadPreviewCountChange, threadPreviewCount], | ||
| 2303 | ); | ||
| 2304 | |||
| 2267 | return ( | 2305 | return ( |
| 2268 | <Menu> | 2306 | <Menu> |
| 2269 | <Tooltip> | 2307 | <Tooltip> |
| ... | @@ -2274,9 +2312,9 @@ function ProjectSortMenu({ | ... | @@ -2274,9 +2312,9 @@ function ProjectSortMenu({ |
| 2274 | > | 2312 | > |
| 2275 | <ArrowUpDownIcon className="size-3.5" /> | 2313 | <ArrowUpDownIcon className="size-3.5" /> |
| 2276 | </TooltipTrigger> | 2314 | </TooltipTrigger> |
| 2277 | <TooltipPopup side="right">Sort projects</TooltipPopup> | 2315 | <TooltipPopup side="right">Sidebar options</TooltipPopup> |
| 2278 | </Tooltip> | 2316 | </Tooltip> |
| 2279 | <MenuPopup align="end" side="bottom" className="min-w-44"> | 2317 | <MenuPopup align="end" side="bottom" className="min-w-52"> |
| 2280 | <MenuGroup> | 2318 | <MenuGroup> |
| 2281 | <div className="px-2 py-1 sm:text-xs font-medium text-muted-foreground"> | 2319 | <div className="px-2 py-1 sm:text-xs font-medium text-muted-foreground"> |
| 2282 | Sort projects | 2320 | Sort projects |
| ... | @@ -2315,6 +2353,42 @@ function ProjectSortMenu({ | ... | @@ -2315,6 +2353,42 @@ function ProjectSortMenu({ |
| 2315 | ))} | 2353 | ))} |
| 2316 | </MenuRadioGroup> | 2354 | </MenuRadioGroup> |
| 2317 | </MenuGroup> | 2355 | </MenuGroup> |
| 2356 | <MenuGroup> | ||
| 2357 | <div className="px-2 pt-2 pb-1 text-muted-foreground sm:text-xs font-medium"> | ||
| 2358 | Visible threads | ||
| 2359 | </div> | ||
| 2360 | <div className="px-2 py-1"> | ||
| 2361 | <NumberField | ||
| 2362 | aria-label="Visible thread count" | ||
| 2363 | className="w-28 gap-0" | ||
| 2364 | max={MAX_SIDEBAR_THREAD_PREVIEW_COUNT} | ||
| 2365 | min={MIN_SIDEBAR_THREAD_PREVIEW_COUNT} | ||
| 2366 | onValueChange={handleThreadPreviewCountChange} | ||
| 2367 | size="sm" | ||
| 2368 | step={1} | ||
| 2369 | value={threadPreviewCount} | ||
| 2370 | > | ||
| 2371 | <NumberFieldGroup className="h-7 rounded-md sm:h-6.5"> | ||
| 2372 | <NumberFieldDecrement | ||
| 2373 | aria-label="Decrease visible thread count" | ||
| 2374 | className="px-2 sm:px-2 [&_svg]:size-3.5" | ||
| 2375 | /> | ||
| 2376 | <NumberFieldInput | ||
| 2377 | aria-label="Visible thread count" | ||
| 2378 | className="h-7 w-9 grow-0 px-0 text-xs leading-7 sm:h-6.5 sm:leading-6.5" | ||
| 2379 | inputMode="numeric" | ||
| 2380 | onKeyDownCapture={(event) => { | ||
| 2381 | event.stopPropagation(); | ||
| 2382 | }} | ||
| 2383 | /> | ||
| 2384 | <NumberFieldIncrement | ||
| 2385 | aria-label="Increase visible thread count" | ||
| 2386 | className="px-2 sm:px-2 [&_svg]:size-3.5" | ||
| 2387 | /> | ||
| 2388 | </NumberFieldGroup> | ||
| 2389 | </NumberField> | ||
| 2390 | </div> | ||
| 2391 | </MenuGroup> | ||
| 2318 | <MenuSeparator /> | 2392 | <MenuSeparator /> |
| 2319 | <MenuGroup> | 2393 | <MenuGroup> |
| 2320 | <div className="px-2 pt-2 pb-1 font-medium text-muted-foreground sm:text-xs"> | 2394 | <div className="px-2 pt-2 pb-1 font-medium text-muted-foreground sm:text-xs"> |
| ... | @@ -2462,6 +2536,7 @@ interface SidebarProjectsContentProps { | ... | @@ -2462,6 +2536,7 @@ interface SidebarProjectsContentProps { |
| 2462 | projectSortOrder: SidebarProjectSortOrder; | 2536 | projectSortOrder: SidebarProjectSortOrder; |
| 2463 | threadSortOrder: SidebarThreadSortOrder; | 2537 | threadSortOrder: SidebarThreadSortOrder; |
| 2464 | projectGroupingMode: SidebarProjectGroupingMode; | 2538 | projectGroupingMode: SidebarProjectGroupingMode; |
| 2539 | threadPreviewCount: SidebarThreadPreviewCount; | ||
| 2465 | updateSettings: ReturnType<typeof useUpdateSettings>["updateSettings"]; | 2540 | updateSettings: ReturnType<typeof useUpdateSettings>["updateSettings"]; |
| 2466 | openAddProject: () => void; | 2541 | openAddProject: () => void; |
| 2467 | isManualProjectSorting: boolean; | 2542 | isManualProjectSorting: boolean; |
| ... | @@ -2502,6 +2577,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( | ... | @@ -2502,6 +2577,7 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( |
| 2502 | projectSortOrder, | 2577 | projectSortOrder, |
| 2503 | threadSortOrder, | 2578 | threadSortOrder, |
| 2504 | projectGroupingMode, | 2579 | projectGroupingMode, |
| 2580 | threadPreviewCount, | ||
| 2505 | updateSettings, | 2581 | updateSettings, |
| 2506 | openAddProject, | 2582 | openAddProject, |
| 2507 | isManualProjectSorting, | 2583 | isManualProjectSorting, |
| ... | @@ -2548,6 +2624,12 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( | ... | @@ -2548,6 +2624,12 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( |
| 2548 | }, | 2624 | }, |
| 2549 | [updateSettings], | 2625 | [updateSettings], |
| 2550 | ); | 2626 | ); |
| 2627 | const handleThreadPreviewCountChange = useCallback( | ||
| 2628 | (count: SidebarThreadPreviewCount) => { | ||
| 2629 | updateSettings({ sidebarThreadPreviewCount: count }); | ||
| 2630 | }, | ||
| 2631 | [updateSettings], | ||
| 2632 | ); | ||
| 2551 | 2633 | ||
| 2552 | return ( | 2634 | return ( |
| 2553 | <SidebarContent className="gap-0"> | 2635 | <SidebarContent className="gap-0"> |
| ... | @@ -2607,9 +2689,11 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( | ... | @@ -2607,9 +2689,11 @@ const SidebarProjectsContent = memo(function SidebarProjectsContent( |
| 2607 | projectSortOrder={projectSortOrder} | 2689 | projectSortOrder={projectSortOrder} |
| 2608 | threadSortOrder={threadSortOrder} | 2690 | threadSortOrder={threadSortOrder} |
| 2609 | projectGroupingMode={projectGroupingMode} | 2691 | projectGroupingMode={projectGroupingMode} |
| 2692 | threadPreviewCount={threadPreviewCount} | ||
| 2610 | onProjectSortOrderChange={handleProjectSortOrderChange} | 2693 | onProjectSortOrderChange={handleProjectSortOrderChange} |
| 2611 | onThreadSortOrderChange={handleThreadSortOrderChange} | 2694 | onThreadSortOrderChange={handleThreadSortOrderChange} |
| 2612 | onProjectGroupingModeChange={handleProjectGroupingModeChange} | 2695 | onProjectGroupingModeChange={handleProjectGroupingModeChange} |
| 2696 | onThreadPreviewCountChange={handleThreadPreviewCountChange} | ||
| 2613 | /> | 2697 | /> |
| 2614 | <Tooltip> | 2698 | <Tooltip> |
| 2615 | <TooltipTrigger | 2699 | <TooltipTrigger |
| ... | @@ -2729,6 +2813,7 @@ export default function Sidebar() { | ... | @@ -2729,6 +2813,7 @@ export default function Sidebar() { |
| 2729 | sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode, | 2813 | sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode, |
| 2730 | sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides, | 2814 | sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides, |
| 2731 | })); | 2815 | })); |
| 2816 | const sidebarThreadPreviewCount = useSettings((s) => s.sidebarThreadPreviewCount); | ||
| 2732 | const { updateSettings } = useUpdateSettings(); | 2817 | const { updateSettings } = useUpdateSettings(); |
| 2733 | const { handleNewThread } = useNewThreadHandler(); | 2818 | const { handleNewThread } = useNewThreadHandler(); |
| 2734 | const { archiveThread, deleteThread } = useThreadActions(); | 2819 | const { archiveThread, deleteThread } = useThreadActions(); |
| ... | @@ -3024,11 +3109,11 @@ export default function Sidebar() { | ... | @@ -3024,11 +3109,11 @@ export default function Sidebar() { |
| 3024 | return []; | 3109 | return []; |
| 3025 | } | 3110 | } |
| 3026 | const isThreadListExpanded = expandedThreadListsByProject.has(project.projectKey); | 3111 | const isThreadListExpanded = expandedThreadListsByProject.has(project.projectKey); |
| 3027 | const hasOverflowingThreads = projectThreads.length > THREAD_PREVIEW_LIMIT; | 3112 | const hasOverflowingThreads = projectThreads.length > sidebarThreadPreviewCount; |
| 3028 | const previewThreads = | 3113 | const previewThreads = |
| 3029 | isThreadListExpanded || !hasOverflowingThreads | 3114 | isThreadListExpanded || !hasOverflowingThreads |
| 3030 | ? projectThreads | 3115 | ? projectThreads |
| 3031 | : projectThreads.slice(0, THREAD_PREVIEW_LIMIT); | 3116 | : projectThreads.slice(0, sidebarThreadPreviewCount); |
| 3032 | const renderedThreads = pinnedCollapsedThread ? [pinnedCollapsedThread] : previewThreads; | 3117 | const renderedThreads = pinnedCollapsedThread ? [pinnedCollapsedThread] : previewThreads; |
| 3033 | return renderedThreads.map((thread) => | 3118 | return renderedThreads.map((thread) => |
| 3034 | scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)), | 3119 | scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)), |
| ... | @@ -3036,6 +3121,7 @@ export default function Sidebar() { | ... | @@ -3036,6 +3121,7 @@ export default function Sidebar() { |
| 3036 | }), | 3121 | }), |
| 3037 | [ | 3122 | [ |
| 3038 | sidebarThreadSortOrder, | 3123 | sidebarThreadSortOrder, |
| 3124 | sidebarThreadPreviewCount, | ||
| 3039 | expandedThreadListsByProject, | 3125 | expandedThreadListsByProject, |
| 3040 | projectExpandedById, | 3126 | projectExpandedById, |
| 3041 | routeThreadKey, | 3127 | routeThreadKey, |
| ... | @@ -3358,6 +3444,7 @@ export default function Sidebar() { | ... | @@ -3358,6 +3444,7 @@ export default function Sidebar() { |
| 3358 | projectSortOrder={sidebarProjectSortOrder} | 3444 | projectSortOrder={sidebarProjectSortOrder} |
| 3359 | threadSortOrder={sidebarThreadSortOrder} | 3445 | threadSortOrder={sidebarThreadSortOrder} |
| 3360 | projectGroupingMode={sidebarProjectGroupingMode} | 3446 | projectGroupingMode={sidebarProjectGroupingMode} |
| 3447 | threadPreviewCount={sidebarThreadPreviewCount} | ||
| 3361 | updateSettings={updateSettings} | 3448 | updateSettings={updateSettings} |
| 3362 | openAddProject={openAddProjectCommandPalette} | 3449 | openAddProject={openAddProjectCommandPalette} |
| 3363 | isManualProjectSorting={isManualProjectSorting} | 3450 | isManualProjectSorting={isManualProjectSorting} |
apps/web/src/components/settings/SettingsPanels.tsx+4| ... | @@ -364,6 +364,9 @@ export function useSettingsRestore(onRestored?: () => void) { | ... | @@ -364,6 +364,9 @@ export function useSettingsRestore(onRestored?: () => void) { |
| 364 | ...(settings.timestampFormat !== DEFAULT_UNIFIED_SETTINGS.timestampFormat | 364 | ...(settings.timestampFormat !== DEFAULT_UNIFIED_SETTINGS.timestampFormat |
| 365 | ? ["Time format"] | 365 | ? ["Time format"] |
| 366 | : []), | 366 | : []), |
| 367 | ...(settings.sidebarThreadPreviewCount !== DEFAULT_UNIFIED_SETTINGS.sidebarThreadPreviewCount | ||
| 368 | ? ["Visible threads"] | ||
| 369 | : []), | ||
| 367 | ...(settings.diffWordWrap !== DEFAULT_UNIFIED_SETTINGS.diffWordWrap | 370 | ...(settings.diffWordWrap !== DEFAULT_UNIFIED_SETTINGS.diffWordWrap |
| 368 | ? ["Diff line wrapping"] | 371 | ? ["Diff line wrapping"] |
| 369 | : []), | 372 | : []), |
| ... | @@ -400,6 +403,7 @@ export function useSettingsRestore(onRestored?: () => void) { | ... | @@ -400,6 +403,7 @@ export function useSettingsRestore(onRestored?: () => void) { |
| 400 | settings.diffIgnoreWhitespace, | 403 | settings.diffIgnoreWhitespace, |
| 401 | settings.diffWordWrap, | 404 | settings.diffWordWrap, |
| 402 | settings.enableAssistantStreaming, | 405 | settings.enableAssistantStreaming, |
| 406 | settings.sidebarThreadPreviewCount, | ||
| 403 | settings.timestampFormat, | 407 | settings.timestampFormat, |
| 404 | theme, | 408 | theme, |
| 405 | ], | 409 | ], |
apps/web/src/components/ui/input.tsx+26-15| ... | @@ -27,6 +27,31 @@ function Input({ | ... | @@ -27,6 +27,31 @@ function Input({ |
| 27 | props.type === "file" && | 27 | props.type === "file" && |
| 28 | "text-muted-foreground file:me-3 file:bg-transparent file:font-medium file:text-foreground file:text-sm", | 28 | "text-muted-foreground file:me-3 file:bg-transparent file:font-medium file:text-foreground file:text-sm", |
| 29 | ); | 29 | ); |
| 30 | let inputElement: React.ReactElement; | ||
| 31 | |||
| 32 | if (nativeInput) { | ||
| 33 | const { style, onValueChange: _onValueChange, ...nativeInputProps } = props; | ||
| 34 | const nativeStyle = typeof style === "function" ? undefined : style; | ||
| 35 | |||
| 36 | inputElement = ( | ||
| 37 | <input | ||
| 38 | className={inputClassName} | ||
| 39 | data-slot="input" | ||
| 40 | size={typeof size === "number" ? size : undefined} | ||
| 41 | style={nativeStyle} | ||
| 42 | {...(nativeInputProps as React.ComponentProps<"input">)} | ||
| 43 | /> | ||
| 44 | ); | ||
| 45 | } else { | ||
| 46 | inputElement = ( | ||
| 47 | <InputPrimitive | ||
| 48 | className={inputClassName} | ||
| 49 | data-slot="input" | ||
| 50 | size={typeof size === "number" ? size : undefined} | ||
| 51 | {...props} | ||
| 52 | /> | ||
| 53 | ); | ||
| 54 | } | ||
| 30 | 55 | ||
| 31 | return ( | 56 | return ( |
| 32 | <span | 57 | <span |
| ... | @@ -40,21 +65,7 @@ function Input({ | ... | @@ -40,21 +65,7 @@ function Input({ |
| 40 | data-size={size} | 65 | data-size={size} |
| 41 | data-slot="input-control" | 66 | data-slot="input-control" |
| 42 | > | 67 | > |
| 43 | {nativeInput ? ( | 68 | {inputElement} |
| 44 | <input | ||
| 45 | className={inputClassName} | ||
| 46 | data-slot="input" | ||
| 47 | size={typeof size === "number" ? size : undefined} | ||
| 48 | {...props} | ||
| 49 | /> | ||
| 50 | ) : ( | ||
| 51 | <InputPrimitive | ||
| 52 | className={inputClassName} | ||
| 53 | data-slot="input" | ||
| 54 | size={typeof size === "number" ? size : undefined} | ||
| 55 | {...props} | ||
| 56 | /> | ||
| 57 | )} | ||
| 58 | </span> | 69 | </span> |
| 59 | ); | 70 | ); |
| 60 | } | 71 | } |
apps/web/src/components/ui/number-field.tsx created+156| ... | @@ -0,0 +1,156 @@ | ||
| 1 | "use client"; | ||
| 2 | |||
| 3 | import { NumberField as NumberFieldPrimitive } from "@base-ui/react/number-field"; | ||
| 4 | import { MinusIcon, PlusIcon } from "lucide-react"; | ||
| 5 | import * as React from "react"; | ||
| 6 | import { cn } from "~/lib/utils"; | ||
| 7 | import { Label } from "~/components/ui/label"; | ||
| 8 | |||
| 9 | export const NumberFieldContext: React.Context<{ | ||
| 10 | fieldId: string; | ||
| 11 | } | null> = React.createContext<{ | ||
| 12 | fieldId: string; | ||
| 13 | } | null>(null); | ||
| 14 | |||
| 15 | export function NumberField({ | ||
| 16 | id, | ||
| 17 | className, | ||
| 18 | size = "default", | ||
| 19 | ...props | ||
| 20 | }: NumberFieldPrimitive.Root.Props & { | ||
| 21 | size?: "sm" | "default" | "lg"; | ||
| 22 | }): React.ReactElement { | ||
| 23 | const generatedId = React.useId(); | ||
| 24 | const fieldId = id ?? generatedId; | ||
| 25 | const contextValue = React.useMemo(() => ({ fieldId }), [fieldId]); | ||
| 26 | |||
| 27 | return ( | ||
| 28 | <NumberFieldContext.Provider value={contextValue}> | ||
| 29 | <NumberFieldPrimitive.Root | ||
| 30 | className={cn("flex w-full flex-col items-start gap-2", className)} | ||
| 31 | data-size={size} | ||
| 32 | data-slot="number-field" | ||
| 33 | id={fieldId} | ||
| 34 | {...props} | ||
| 35 | /> | ||
| 36 | </NumberFieldContext.Provider> | ||
| 37 | ); | ||
| 38 | } | ||
| 39 | |||
| 40 | export function NumberFieldGroup({ | ||
| 41 | className, | ||
| 42 | ...props | ||
| 43 | }: NumberFieldPrimitive.Group.Props): React.ReactElement { | ||
| 44 | return ( | ||
| 45 | <NumberFieldPrimitive.Group | ||
| 46 | className={cn( | ||
| 47 | "relative flex w-full justify-between rounded-lg border border-input bg-background not-dark:bg-clip-padding text-base text-foreground shadow-xs/5 ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-data-disabled:not-focus-within:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] focus-within:border-ring focus-within:ring-[3px] has-aria-invalid:border-destructive/36 has-autofill:bg-foreground/4 focus-within:has-aria-invalid:border-destructive/64 focus-within:has-aria-invalid:ring-destructive/48 data-disabled:pointer-events-none data-disabled:opacity-64 sm:text-sm dark:bg-input/32 dark:has-autofill:bg-foreground/8 dark:has-aria-invalid:ring-destructive/24 dark:not-data-disabled:not-focus-within:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 [[data-disabled],:focus-within,[aria-invalid]]:shadow-none", | ||
| 48 | className, | ||
| 49 | )} | ||
| 50 | data-slot="number-field-group" | ||
| 51 | {...props} | ||
| 52 | /> | ||
| 53 | ); | ||
| 54 | } | ||
| 55 | |||
| 56 | export function NumberFieldDecrement({ | ||
| 57 | className, | ||
| 58 | ...props | ||
| 59 | }: NumberFieldPrimitive.Decrement.Props): React.ReactElement { | ||
| 60 | return ( | ||
| 61 | <NumberFieldPrimitive.Decrement | ||
| 62 | className={cn( | ||
| 63 | "relative flex shrink-0 cursor-pointer items-center justify-center rounded-s-[calc(var(--radius-lg)-1px)] in-data-[size=sm]:px-[calc(--spacing(2.5)-1px)] px-[calc(--spacing(3)-1px)] transition-colors pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:bg-accent", | ||
| 64 | className, | ||
| 65 | )} | ||
| 66 | data-slot="number-field-decrement" | ||
| 67 | {...props} | ||
| 68 | > | ||
| 69 | <MinusIcon /> | ||
| 70 | </NumberFieldPrimitive.Decrement> | ||
| 71 | ); | ||
| 72 | } | ||
| 73 | |||
| 74 | export function NumberFieldIncrement({ | ||
| 75 | className, | ||
| 76 | ...props | ||
| 77 | }: NumberFieldPrimitive.Increment.Props): React.ReactElement { | ||
| 78 | return ( | ||
| 79 | <NumberFieldPrimitive.Increment | ||
| 80 | className={cn( | ||
| 81 | "relative flex shrink-0 cursor-pointer items-center justify-center rounded-e-[calc(var(--radius-lg)-1px)] in-data-[size=sm]:px-[calc(--spacing(2.5)-1px)] px-[calc(--spacing(3)-1px)] transition-colors pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:bg-accent", | ||
| 82 | className, | ||
| 83 | )} | ||
| 84 | data-slot="number-field-increment" | ||
| 85 | {...props} | ||
| 86 | > | ||
| 87 | <PlusIcon /> | ||
| 88 | </NumberFieldPrimitive.Increment> | ||
| 89 | ); | ||
| 90 | } | ||
| 91 | |||
| 92 | export function NumberFieldInput({ | ||
| 93 | className, | ||
| 94 | ...props | ||
| 95 | }: NumberFieldPrimitive.Input.Props): React.ReactElement { | ||
| 96 | return ( | ||
| 97 | <NumberFieldPrimitive.Input | ||
| 98 | className={cn( | ||
| 99 | "h-8.5 in-data-[size=lg]:h-9.5 in-data-[size=sm]:h-7.5 w-full min-w-0 grow bg-transparent in-data-[size=sm]:px-[calc(--spacing(2.5)-1px)] px-[calc(--spacing(3)-1px)] text-center tabular-nums in-data-[size=lg]:leading-9.5 in-data-[size=sm]:leading-7.5 leading-8.5 outline-none [transition:background-color_5000000s_ease-in-out_0s] sm:h-7.5 sm:in-data-[size=lg]:h-8.5 sm:in-data-[size=sm]:h-6.5 sm:in-data-[size=lg]:leading-8.5 sm:in-data-[size=sm]:leading-8.5 sm:leading-7.5", | ||
| 100 | className, | ||
| 101 | )} | ||
| 102 | data-slot="number-field-input" | ||
| 103 | {...props} | ||
| 104 | /> | ||
| 105 | ); | ||
| 106 | } | ||
| 107 | |||
| 108 | export function NumberFieldScrubArea({ | ||
| 109 | className, | ||
| 110 | label, | ||
| 111 | ...props | ||
| 112 | }: NumberFieldPrimitive.ScrubArea.Props & { | ||
| 113 | label: string; | ||
| 114 | }): React.ReactElement { | ||
| 115 | const context = React.useContext(NumberFieldContext); | ||
| 116 | |||
| 117 | if (!context) { | ||
| 118 | throw new Error( | ||
| 119 | "NumberFieldScrubArea must be used within a NumberField component for accessibility.", | ||
| 120 | ); | ||
| 121 | } | ||
| 122 | |||
| 123 | return ( | ||
| 124 | <NumberFieldPrimitive.ScrubArea | ||
| 125 | className={cn("flex cursor-ew-resize", className)} | ||
| 126 | data-slot="number-field-scrub-area" | ||
| 127 | {...props} | ||
| 128 | > | ||
| 129 | <Label className="cursor-ew-resize" htmlFor={context.fieldId}> | ||
| 130 | {label} | ||
| 131 | </Label> | ||
| 132 | <NumberFieldPrimitive.ScrubAreaCursor className="drop-shadow-[0_1px_1px_#0008] filter"> | ||
| 133 | <CursorGrowIcon /> | ||
| 134 | </NumberFieldPrimitive.ScrubAreaCursor> | ||
| 135 | </NumberFieldPrimitive.ScrubArea> | ||
| 136 | ); | ||
| 137 | } | ||
| 138 | |||
| 139 | export function CursorGrowIcon(props: React.ComponentProps<"svg">): React.ReactElement { | ||
| 140 | return ( | ||
| 141 | <svg | ||
| 142 | aria-hidden="true" | ||
| 143 | fill="black" | ||
| 144 | height="14" | ||
| 145 | stroke="white" | ||
| 146 | viewBox="0 0 24 14" | ||
| 147 | width="26" | ||
| 148 | xmlns="http://www.w3.org/2000/svg" | ||
| 149 | {...props} | ||
| 150 | > | ||
| 151 | <path d="M19.5 5.5L6.49737 5.51844V2L1 6.9999L6.5 12L6.49737 8.5L19.5 8.5V12L25 6.9999L19.5 2V5.5Z" /> | ||
| 152 | </svg> | ||
| 153 | ); | ||
| 154 | } | ||
| 155 | |||
| 156 | export { NumberFieldPrimitive }; | ||
apps/web/src/localApi.test.ts+2| ... | @@ -613,6 +613,7 @@ describe("wsApi", () => { | ... | @@ -613,6 +613,7 @@ describe("wsApi", () => { |
| 613 | }, | 613 | }, |
| 614 | sidebarProjectSortOrder: "manual" as const, | 614 | sidebarProjectSortOrder: "manual" as const, |
| 615 | sidebarThreadSortOrder: "created_at" as const, | 615 | sidebarThreadSortOrder: "created_at" as const, |
| 616 | sidebarThreadPreviewCount: 6, | ||
| 616 | timestampFormat: "24-hour" as const, | 617 | timestampFormat: "24-hour" as const, |
| 617 | }; | 618 | }; |
| 618 | const getClientSettings = vi.fn().mockResolvedValue({ | 619 | const getClientSettings = vi.fn().mockResolvedValue({ |
| ... | @@ -675,6 +676,7 @@ describe("wsApi", () => { | ... | @@ -675,6 +676,7 @@ describe("wsApi", () => { |
| 675 | }, | 676 | }, |
| 676 | sidebarProjectSortOrder: "manual" as const, | 677 | sidebarProjectSortOrder: "manual" as const, |
| 677 | sidebarThreadSortOrder: "created_at" as const, | 678 | sidebarThreadSortOrder: "created_at" as const, |
| 679 | sidebarThreadPreviewCount: 6, | ||
| 678 | timestampFormat: "24-hour" as const, | 680 | timestampFormat: "24-hour" as const, |
| 679 | }; | 681 | }; |
| 680 | 682 |
apps/web/vite.config.ts-2| ... | @@ -7,7 +7,6 @@ import pkg from "./package.json" with { type: "json" }; | ... | @@ -7,7 +7,6 @@ import pkg from "./package.json" with { type: "json" }; |
| 7 | 7 | ||
| 8 | const port = Number(process.env.PORT ?? 5733); | 8 | const port = Number(process.env.PORT ?? 5733); |
| 9 | const host = process.env.HOST?.trim() || "localhost"; | 9 | const host = process.env.HOST?.trim() || "localhost"; |
| 10 | const configuredHttpUrl = process.env.VITE_HTTP_URL?.trim(); | ||
| 11 | const configuredWsUrl = process.env.VITE_WS_URL?.trim(); | 10 | const configuredWsUrl = process.env.VITE_WS_URL?.trim(); |
| 12 | const configuredHostedAppUrl = (() => { | 11 | const configuredHostedAppUrl = (() => { |
| 13 | if (process.env.VERCEL_ENV === "production" && process.env.VERCEL_PROJECT_PRODUCTION_URL) { | 12 | if (process.env.VERCEL_ENV === "production" && process.env.VERCEL_PROJECT_PRODUCTION_URL) { |
| ... | @@ -74,7 +73,6 @@ export default defineConfig({ | ... | @@ -74,7 +73,6 @@ export default defineConfig({ |
| 74 | ], | 73 | ], |
| 75 | }, | 74 | }, |
| 76 | define: { | 75 | define: { |
| 77 | "import.meta.env.VITE_HTTP_URL": JSON.stringify(configuredHttpUrl ?? ""), | ||
| 78 | // In dev mode, tell the web app where the WebSocket server lives | 76 | // In dev mode, tell the web app where the WebSocket server lives |
| 79 | "import.meta.env.VITE_WS_URL": JSON.stringify(configuredWsUrl ?? ""), | 77 | "import.meta.env.VITE_WS_URL": JSON.stringify(configuredWsUrl ?? ""), |
| 80 | "import.meta.env.VITE_HOSTED_APP_URL": JSON.stringify(configuredHostedAppUrl ?? ""), | 78 | "import.meta.env.VITE_HOSTED_APP_URL": JSON.stringify(configuredHostedAppUrl ?? ""), |
bun.lock+11-7| ... | @@ -16,7 +16,7 @@ | ... | @@ -16,7 +16,7 @@ |
| 16 | }, | 16 | }, |
| 17 | "apps/desktop": { | 17 | "apps/desktop": { |
| 18 | "name": "@t3tools/desktop", | 18 | "name": "@t3tools/desktop", |
| 19 | "version": "0.0.21", | 19 | "version": "0.0.22", |
| 20 | "dependencies": { | 20 | "dependencies": { |
| 21 | "@effect/platform-node": "catalog:", | 21 | "@effect/platform-node": "catalog:", |
| 22 | "effect": "catalog:", | 22 | "effect": "catalog:", |
| ... | @@ -49,7 +49,7 @@ | ... | @@ -49,7 +49,7 @@ |
| 49 | }, | 49 | }, |
| 50 | "apps/server": { | 50 | "apps/server": { |
| 51 | "name": "t3", | 51 | "name": "t3", |
| 52 | "version": "0.0.21", | 52 | "version": "0.0.22", |
| 53 | "bin": { | 53 | "bin": { |
| 54 | "t3": "./dist/bin.mjs", | 54 | "t3": "./dist/bin.mjs", |
| 55 | }, | 55 | }, |
| ... | @@ -82,9 +82,9 @@ | ... | @@ -82,9 +82,9 @@ |
| 82 | }, | 82 | }, |
| 83 | "apps/web": { | 83 | "apps/web": { |
| 84 | "name": "@t3tools/web", | 84 | "name": "@t3tools/web", |
| 85 | "version": "0.0.21", | 85 | "version": "0.0.22", |
| 86 | "dependencies": { | 86 | "dependencies": { |
| 87 | "@base-ui/react": "^1.2.0", | 87 | "@base-ui/react": "^1.4.1", |
| 88 | "@dnd-kit/core": "^6.3.1", | 88 | "@dnd-kit/core": "^6.3.1", |
| 89 | "@dnd-kit/modifiers": "^9.0.0", | 89 | "@dnd-kit/modifiers": "^9.0.0", |
| 90 | "@dnd-kit/sortable": "^10.0.0", | 90 | "@dnd-kit/sortable": "^10.0.0", |
| ... | @@ -148,7 +148,7 @@ | ... | @@ -148,7 +148,7 @@ |
| 148 | }, | 148 | }, |
| 149 | "packages/contracts": { | 149 | "packages/contracts": { |
| 150 | "name": "@t3tools/contracts", | 150 | "name": "@t3tools/contracts", |
| 151 | "version": "0.0.21", | 151 | "version": "0.0.22", |
| 152 | "dependencies": { | 152 | "dependencies": { |
| 153 | "effect": "catalog:", | 153 | "effect": "catalog:", |
| 154 | }, | 154 | }, |
| ... | @@ -350,9 +350,9 @@ | ... | @@ -350,9 +350,9 @@ |
| 350 | 350 | ||
| 351 | "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], | 351 | "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], |
| 352 | 352 | ||
| 353 | "@base-ui/react": ["@base-ui/react@1.3.0", "", { "dependencies": { "@babel/runtime": "^7.28.6", "@base-ui/utils": "0.2.6", "@floating-ui/react-dom": "^2.1.8", "@floating-ui/utils": "^0.2.11", "tabbable": "^6.4.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-FwpKqZbPz14AITp1CVgf4AjhKPe1OeeVKSBMdgD10zbFlj3QSWelmtCMLi2+/PFZZcIm3l87G7rwtCZJwHyXWA=="], | 353 | "@base-ui/react": ["@base-ui/react@1.4.1", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@base-ui/utils": "0.2.8", "@floating-ui/react-dom": "^2.1.8", "@floating-ui/utils": "^0.2.11", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@date-fns/tz": "^1.2.0", "@types/react": "^17 || ^18 || ^19", "date-fns": "^4.0.0", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@date-fns/tz", "@types/react", "date-fns"] }, "sha512-Ab5/LIhcmL8BQcsBUYiOfkSDRdLpvgUBzMK30cu684JPcLclYlztharvCZyNNgzJtbAiREzI9q0pI5erHCMgCw=="], |
| 354 | 354 | ||
| 355 | "@base-ui/utils": ["@base-ui/utils@0.2.6", "", { "dependencies": { "@babel/runtime": "^7.28.6", "@floating-ui/utils": "^0.2.11", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-yQ+qeuqohwhsNpoYDqqXaLllYAkPCP4vYdDrVo8FQXaAPfHWm1pG/Vm+jmGTA5JFS0BAIjookyapuJFY8F9PIw=="], | 355 | "@base-ui/utils": ["@base-ui/utils@0.2.8", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@floating-ui/utils": "^0.2.11", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-jvOi+c+ftGlGotNcKnzPVg2IhCaDTB6/6R3JeqdjdXktuAJi3wKH9T7+svuaKh1mmfVU11UWzUZVH74JDfi/wQ=="], |
| 356 | 356 | ||
| 357 | "@blazediff/core": ["@blazediff/core@1.9.1", "", {}, "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA=="], | 357 | "@blazediff/core": ["@blazediff/core@1.9.1", "", {}, "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA=="], |
| 358 | 358 | ||
| ... | @@ -2122,6 +2122,10 @@ | ... | @@ -2122,6 +2122,10 @@ |
| 2122 | 2122 | ||
| 2123 | "@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], | 2123 | "@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], |
| 2124 | 2124 | ||
| 2125 | "@base-ui/react/@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], | ||
| 2126 | |||
| 2127 | "@base-ui/utils/@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], | ||
| 2128 | |||
| 2125 | "@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], | 2129 | "@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], |
| 2126 | 2130 | ||
| 2127 | "@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], | 2131 | "@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], |
packages/contracts/src/settings.ts+13| ... | @@ -27,6 +27,16 @@ export const SidebarProjectGroupingMode = Schema.Literals([ | ... | @@ -27,6 +27,16 @@ export const SidebarProjectGroupingMode = Schema.Literals([ |
| 27 | ]); | 27 | ]); |
| 28 | export type SidebarProjectGroupingMode = typeof SidebarProjectGroupingMode.Type; | 28 | export type SidebarProjectGroupingMode = typeof SidebarProjectGroupingMode.Type; |
| 29 | export const DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE: SidebarProjectGroupingMode = "repository"; | 29 | export const DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE: SidebarProjectGroupingMode = "repository"; |
| 30 | export const MIN_SIDEBAR_THREAD_PREVIEW_COUNT = 1; | ||
| 31 | export const MAX_SIDEBAR_THREAD_PREVIEW_COUNT = 15; | ||
| 32 | export const SidebarThreadPreviewCount = Schema.Int.check( | ||
| 33 | Schema.isBetween({ | ||
| 34 | minimum: MIN_SIDEBAR_THREAD_PREVIEW_COUNT, | ||
| 35 | maximum: MAX_SIDEBAR_THREAD_PREVIEW_COUNT, | ||
| 36 | }), | ||
| 37 | ); | ||
| 38 | export type SidebarThreadPreviewCount = typeof SidebarThreadPreviewCount.Type; | ||
| 39 | export const DEFAULT_SIDEBAR_THREAD_PREVIEW_COUNT: SidebarThreadPreviewCount = 6; | ||
| 30 | 40 | ||
| 31 | export const ClientSettingsSchema = Schema.Struct({ | 41 | export const ClientSettingsSchema = Schema.Struct({ |
| 32 | autoOpenPlanSidebar: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), | 42 | autoOpenPlanSidebar: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), |
| ... | @@ -75,6 +85,9 @@ export const ClientSettingsSchema = Schema.Struct({ | ... | @@ -75,6 +85,9 @@ export const ClientSettingsSchema = Schema.Struct({ |
| 75 | sidebarThreadSortOrder: SidebarThreadSortOrder.pipe( | 85 | sidebarThreadSortOrder: SidebarThreadSortOrder.pipe( |
| 76 | Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_THREAD_SORT_ORDER)), | 86 | Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_THREAD_SORT_ORDER)), |
| 77 | ), | 87 | ), |
| 88 | sidebarThreadPreviewCount: SidebarThreadPreviewCount.pipe( | ||
| 89 | Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_THREAD_PREVIEW_COUNT)), | ||
| 90 | ), | ||
| 78 | timestampFormat: TimestampFormat.pipe( | 91 | timestampFormat: TimestampFormat.pipe( |
| 79 | Schema.withDecodingDefault(Effect.succeed(DEFAULT_TIMESTAMP_FORMAT)), | 92 | Schema.withDecodingDefault(Effect.succeed(DEFAULT_TIMESTAMP_FORMAT)), |
| 80 | ), | 93 | ), |