Skip to content

feat(web): workspace date filters + mobile terminal focus fix#4655

Merged
AviPeltz merged 3 commits into
mainfrom
workspace-date-filters
May 17, 2026
Merged

feat(web): workspace date filters + mobile terminal focus fix#4655
AviPeltz merged 3 commits into
mainfrom
workspace-date-filters

Conversation

@AviPeltz
Copy link
Copy Markdown
Collaborator

@AviPeltz AviPeltz commented May 17, 2026

Summary

  • Adds sort (Recently created / Oldest / Name) and a created-within filter (7/30/90 days) to the /workspaces page, with a relative "created Nd ago" label on each row. v2Workspace.list now returns createdAt.
  • Fixes a mobile bug where focusing the web terminal scrolled the page to the top: the hidden capture input has been moved above the keyboard, and scroll position is defensively restored across the next two animation frames to defeat iOS Safari's keyboard-avoidance scroll.

Test plan

  • /workspaces — confirm new "Any time / 7d / 30d / 90d" and sort dropdowns render, filter and reorder rows, and the "created Nd ago" label appears
  • /workspaces — verify project/host/search filters still work and compose with the new ones
  • Mobile Safari on a workspace terminal — tap to focus and confirm the page no longer jumps to the top; keyboard still appears and typing still goes through
  • Mobile Chrome — same flow, no regression
  • Desktop — clicking the terminal still focuses xterm via the existing mouse path

Open in Stage

Summary by cubic

Adds created-date sorting and a “created Nd ago” label to the Workspaces list, fixes the mobile terminal focus jump, and corrects the relative time rounding near boundaries.

  • New Features

    • Sort by Recently created, Oldest, or Name on /workspaces.
    • Filter by created within 7/30/90 days; composes with project/host/search.
    • v2Workspace.list now returns createdAt for client-side sort/filter.
  • Bug Fixes

    • Mobile terminal focus no longer scrolls to the top on iOS.
    • Moved the hidden input above the keyboard and restored scroll across frames.
    • Relative time (“created Nd ago”) no longer rolls over early; uses Math.floor for each unit.

Written for commit 02753cf. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features

    • Workspaces display relative "created ..." timestamps
    • Added "created within" date filter for workspaces
    • Added sorting options: most recent, oldest, or name
  • Bug Fixes

    • Fixed iOS Safari issue where focusing the on-screen keyboard caused unwanted page scrolling by preserving and restoring scroll position

Review Change Stack

AviPeltz added 2 commits May 16, 2026 17:26
Adds a "Recently created / Oldest / Name" sort and a "created within
7/30/90 days" filter to the /workspaces list, and surfaces a relative
"created Nd ago" label on each row. v2Workspace.list now returns
createdAt so the client can sort/filter without an extra round-trip.
The hidden input used to capture mobile keystrokes sat at the bottom of
the layout viewport. On iOS Safari, focusing it triggered the keyboard-
avoidance scroll, which on a viewport-height page yanked the whole
terminal to the top.

Move the input above the keyboard with pointer-events-none, and
defensively restore the scroll position across the next two animation
frames in case the OS scroll fires after focus.
@capy-ai
Copy link
Copy Markdown

capy-ai Bot commented May 17, 2026

Capy auto-review is paused for this organization because the monthly auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

@stage-review
Copy link
Copy Markdown

stage-review Bot commented May 17, 2026

Ready to review this PR? Stage has broken it down into 3 individual chapters for you:

Title
1 Expose createdAt in workspace list API
2 Implement workspace date filtering and sorting
3 Fix mobile terminal focus scroll behavior
Open in Stage

Chapters generated by Stage for commit 02753cf on May 17, 2026 12:44am UTC.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c3af6b1e-e0a3-4af1-9c62-6eeda444a437

📥 Commits

Reviewing files that changed from the base of the PR and between 7a4f249 and 02753cf.

📒 Files selected for processing (1)
  • apps/web/src/app/workspaces/page.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/src/app/workspaces/page.tsx

📝 Walkthrough

Walkthrough

Adds workspace creation-date filtering and sorting (new UI controls and relative timestamps) backed by an API change returning createdAt, and fixes iOS Safari scroll behavior in MobileTerminalInput by preserving/restoring scroll on textarea focus.

Changes

Workspace creation time filtering and sorting

Layer / File(s) Summary
Backend workspace list API enhancement
packages/trpc/src/router/v2-workspace/v2-workspace.ts
v2WorkspaceRouter.list database query now selects createdAt from v2Workspaces, and the response mapping includes createdAt on each returned workspace object.
Frontend type contracts and helper functions
apps/web/src/app/workspaces/page.tsx
WorkspaceRow type gains createdAt: Date field; SortBy and CreatedWithin enums and CREATED_WITHIN_DAYS constants define available filtering/sorting options; formatRelative helper converts createdAt to relative timestamps.
Frontend state and data loading
apps/web/src/app/workspaces/page.tsx
Component state adds sortBy and createdWithin variables; workspace list loading maps backend row.createdAt into WorkspaceRow.createdAt.
Frontend filtering and sorting logic
apps/web/src/app/workspaces/page.tsx
visibleWorkspaces filtering applies the createdWithin time-window cutoff and sorts by selected sortBy option (recent, oldest, or name) replacing the fixed alphabetical sort.
Frontend UI controls and timestamp display
apps/web/src/app/workspaces/page.tsx
New dropdown controls for createdWithin (Any time / Last 7/30/90 days) and sortBy (Recently created / Oldest first / Name A–Z) wired to state; workspace items render relative "created …" timestamp.

Mobile terminal input iOS scroll fix

Layer / File(s) Summary
Scroll preservation on keyboard focus
apps/web/src/components/MobileTerminalInput/MobileTerminalInput.tsx
focusKeyboardInput captures scroll state before textarea focus and restores both element and window scroll positions via nested requestAnimationFrame to prevent iOS Safari auto-scroll; textarea repositioned from bottom-left to top-left with pointer-events-none.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Fresh timestamps bloom on workspace rows,
I count the days and sort them slow,
Dropdowns hum, the list obeys,
iOS no longer steals the page,
A rabbit's hop brings calm to code.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the two main changes: workspace date filters and mobile terminal focus fix.
Description check ✅ Passed The description covers all required sections: clear summary of changes, test plan with specific steps, and additional context. It aligns well with the description template.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch workspace-date-filters

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/web/src/components/MobileTerminalInput/MobileTerminalInput.tsx (1)

34-65: ⚡ Quick win

Cancel queued scroll-restore frames to prevent stale scroll jumps.

requestAnimationFrame callbacks are currently fire-and-forget. On repeated focus attempts, older callbacks can still run and restore outdated coordinates. Consider canceling prior frame IDs before scheduling new ones, and on unmount.

Proposed patch
 export function MobileTerminalInput({
   focusTargetRef,
   onSend,
   onFocusTerminal,
   enabled = true,
   toolbarVisibility = "mobile",
 }: MobileTerminalInputProps) {
 	const textareaRef = useRef<HTMLTextAreaElement | null>(null);
 	const isComposingRef = useRef(false);
+	const restoreRaf1Ref = useRef<number | null>(null);
+	const restoreRaf2Ref = useRef<number | null>(null);

 	const focusKeyboardInput = useCallback(() => {
 		if (!enabled) return;
+		if (restoreRaf1Ref.current !== null) {
+			cancelAnimationFrame(restoreRaf1Ref.current);
+			restoreRaf1Ref.current = null;
+		}
+		if (restoreRaf2Ref.current !== null) {
+			cancelAnimationFrame(restoreRaf2Ref.current);
+			restoreRaf2Ref.current = null;
+		}
 		const scrollingElement =
 			document.scrollingElement ?? document.documentElement;
@@
-		requestAnimationFrame(() => {
+		restoreRaf1Ref.current = requestAnimationFrame(() => {
 			restore();
-			requestAnimationFrame(restore);
+			restoreRaf2Ref.current = requestAnimationFrame(() => {
+				restore();
+				restoreRaf2Ref.current = null;
+			});
+			restoreRaf1Ref.current = null;
 		});
 	}, [enabled]);
+
+	useEffect(() => {
+		return () => {
+			if (restoreRaf1Ref.current !== null) {
+				cancelAnimationFrame(restoreRaf1Ref.current);
+			}
+			if (restoreRaf2Ref.current !== null) {
+				cancelAnimationFrame(restoreRaf2Ref.current);
+			}
+		};
+	}, []);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/MobileTerminalInput/MobileTerminalInput.tsx` around
lines 34 - 65, The focusKeyboardInput function schedules two
requestAnimationFrame callbacks that can become stale; store the returned frame
IDs (e.g., in a ref like restoreFrameRef) and before scheduling new frames
cancel any existing IDs with cancelAnimationFrame, then schedule the two frames
and save their IDs; also cancel any pending frames in a useEffect
cleanup/unmount to avoid old restore() calls restoring outdated scroll
coordinates. Ensure references to textareaRef and the restore inner function
remain unchanged while managing the frame IDs.
apps/web/src/app/workspaces/page.tsx (1)

294-314: ⚡ Quick win

Add explicit accessible labels to the new filter selects.

The newly added controls need explicit names for screen readers (aria-label or <label htmlFor>).

Proposed fix
 					<select
 						value={createdWithin}
 						onChange={(event) =>
 							setCreatedWithin(event.target.value as CreatedWithin)
 						}
+						aria-label="Filter by creation date"
 						className="rounded-md border bg-transparent px-3 py-2 text-sm"
 					>
@@
 					<select
 						value={sortBy}
 						onChange={(event) => setSortBy(event.target.value as SortBy)}
+						aria-label="Sort workspaces"
 						className="rounded-md border bg-transparent px-3 py-2 text-sm"
 					>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/workspaces/page.tsx` around lines 294 - 314, The two new
select controls for filtering (the one bound to createdWithin / setCreatedWithin
and the one bound to sortBy / setSortBy) lack accessible labels; add explicit
accessible names by either associating each select with a <label htmlFor="...">
and matching id on the select or by adding an aria-label attribute (e.g.,
aria-label="Created within" for the createdWithin select and aria-label="Sort
by" for the sortBy select) so screen readers can announce their purpose; ensure
the chosen ids/aria-label strings are descriptive and update any type casts
(CreatedWithin, SortBy) unaffected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/app/workspaces/page.tsx`:
- Around line 26-38: The formatRelative function uses Math.round which causes
premature rollovers (e.g., 23.5h → 1d); change the rounding strategy to use
Math.floor for all unit conversions in formatRelative so minutes, hours, days,
months and years are computed with Math.floor (keep the initial minutes < 1
check as-is) to provide stable "N unit(s) ago" labels; update the conversions in
function formatRelative accordingly (replace Math.round with Math.floor for
minutes, hours, days, months, years).

---

Nitpick comments:
In `@apps/web/src/app/workspaces/page.tsx`:
- Around line 294-314: The two new select controls for filtering (the one bound
to createdWithin / setCreatedWithin and the one bound to sortBy / setSortBy)
lack accessible labels; add explicit accessible names by either associating each
select with a <label htmlFor="..."> and matching id on the select or by adding
an aria-label attribute (e.g., aria-label="Created within" for the createdWithin
select and aria-label="Sort by" for the sortBy select) so screen readers can
announce their purpose; ensure the chosen ids/aria-label strings are descriptive
and update any type casts (CreatedWithin, SortBy) unaffected.

In `@apps/web/src/components/MobileTerminalInput/MobileTerminalInput.tsx`:
- Around line 34-65: The focusKeyboardInput function schedules two
requestAnimationFrame callbacks that can become stale; store the returned frame
IDs (e.g., in a ref like restoreFrameRef) and before scheduling new frames
cancel any existing IDs with cancelAnimationFrame, then schedule the two frames
and save their IDs; also cancel any pending frames in a useEffect
cleanup/unmount to avoid old restore() calls restoring outdated scroll
coordinates. Ensure references to textareaRef and the restore inner function
remain unchanged while managing the frame IDs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fa35bddc-9831-4d59-b3d4-f1815d333207

📥 Commits

Reviewing files that changed from the base of the PR and between da7ab94 and 7a4f249.

📒 Files selected for processing (3)
  • apps/web/src/app/workspaces/page.tsx
  • apps/web/src/components/MobileTerminalInput/MobileTerminalInput.tsx
  • packages/trpc/src/router/v2-workspace/v2-workspace.ts

Comment thread apps/web/src/app/workspaces/page.tsx
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 3 files

Re-trigger cubic

@AviPeltz
Copy link
Copy Markdown
Collaborator Author

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 17, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 17, 2026

Greptile Summary

This PR adds sort and date-range filters to the /workspaces page (surfacing createdAt from the tRPC v2Workspace.list endpoint), and fixes a mobile bug where focusing the hidden terminal capture input caused iOS Safari to scroll the page to the top.

  • Workspace filters: Adds "Recently created / Oldest / Name" sort and "Any time / 7d / 30d / 90d" created-within filter; each row now shows a relative "created Nd ago" label via a client-side formatRelative helper.
  • tRPC change: createdAt is projected into the list query SELECT and the returned row map — a minimal, isolated addition.
  • iOS Safari scroll fix: The hidden <textarea> is repositioned from bottom-0 to top-0, pointer-events-none is added, and a two-frame requestAnimationFrame restore loop defensively resets scroll position after focus() is called.

Confidence Score: 4/5

Safe to merge; the workspace filter changes are additive client-side logic with no backend risk, and the iOS scroll fix is targeted and well-commented.

The workspace page changes are straightforward client-side filtering — the tRPC change is a single field addition with no migration needed. The mobile terminal fix correctly addresses the iOS Safari keyboard-scroll bug, but the pre-existing double-firing of focusKeyboardInput means two independent scroll-restore rAF chains are scheduled on every touch event. In async-scroll scenarios this is harmless, but in synchronous-scroll scenarios the second chain could restore the page to the displaced position and defeat the fix.

MobileTerminalInput.tsx deserves a second look around the double-fire of focusKeyboardInput — specifically whether the touchstart listener is still needed now that the non-mouse pointerdown branch handles touch.

Important Files Changed

Filename Overview
apps/web/src/app/workspaces/page.tsx Adds sort (recent/oldest/name) and createdWithin (all/7d/30d/90d) filters plus a relative-time label; logic in useMemo is correct, though formatRelative timestamps freeze at render time.
apps/web/src/components/MobileTerminalInput/MobileTerminalInput.tsx Moves hidden textarea to top-0 and adds two-frame rAF scroll restore to defeat iOS Safari keyboard scroll; the fix is sound but the double-fire of focusKeyboardInput (touchstart + non-mouse pointerdown) could produce conflicting rAF restore chains if iOS scroll fires synchronously.
packages/trpc/src/router/v2-workspace/v2-workspace.ts Minimal addition of createdAt to the list query SELECT and the returned row shape; isolated and mechanically correct.

Sequence Diagram

sequenceDiagram
    participant User
    participant WorkspacesPage
    participant tRPC
    participant DB

    User->>WorkspacesPage: Load /workspaces
    WorkspacesPage->>tRPC: "v2Workspace.list({ organizationId })"
    tRPC->>DB: SELECT id, name, branch, projectId, projectName, hostId, createdAt
    DB-->>tRPC: rows[]
    tRPC-->>WorkspacesPage: rows with createdAt
    WorkspacesPage->>WorkspacesPage: map rows to WorkspaceRow new Date(createdAt)

    User->>WorkspacesPage: Change sort or createdWithin filter
    WorkspacesPage->>WorkspacesPage: useMemo recomputes visibleWorkspaces
    WorkspacesPage-->>User: Re-rendered list with created Nd ago labels

    Note over User,WorkspacesPage: Mobile terminal focus fix
    User->>WorkspacesPage: Tap terminal touchstart then pointerdown
    WorkspacesPage->>WorkspacesPage: focusKeyboardInput called twice
    WorkspacesPage->>WorkspacesPage: save scrollTop
    WorkspacesPage->>WorkspacesPage: textarea.focus preventScroll true
    WorkspacesPage->>WorkspacesPage: rAF restore scroll across 2 frames
Loading

Comments Outside Diff (1)

  1. apps/web/src/components/MobileTerminalInput/MobileTerminalInput.tsx, line 67-88 (link)

    P2 Double focusKeyboardInput call creates conflicting restore cycles

    Both onTouchStart and the non-mouse branch of onPointerDown call focusKeyboardInput. On iOS touch, both events fire for the same gesture — touchstart fires before pointerdown — so two independent save/restore cycles are scheduled. If iOS's keyboard-avoidance scroll fires synchronously during the first focus() call, the second call captures the post-scroll (displaced) position as its savedScrollTop and its rAF chain restores the page to that displaced position, after the first chain has already corrected it. In async-scroll scenarios this is harmless, but the code would silently regress when iOS behavior changes.

    Consider guarding focusKeyboardInput with an in-flight flag or removing the now-redundant onTouchStart listener, since onPointerDown with pointerType !== "mouse" already covers touch inputs.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/web/src/components/MobileTerminalInput/MobileTerminalInput.tsx
    Line: 67-88
    
    Comment:
    **Double `focusKeyboardInput` call creates conflicting restore cycles**
    
    Both `onTouchStart` and the non-mouse branch of `onPointerDown` call `focusKeyboardInput`. On iOS touch, both events fire for the same gesture — `touchstart` fires before `pointerdown` — so two independent save/restore cycles are scheduled. If iOS's keyboard-avoidance scroll fires synchronously during the first `focus()` call, the second call captures the post-scroll (displaced) position as its `savedScrollTop` and its rAF chain restores the page to that displaced position, after the first chain has already corrected it. In async-scroll scenarios this is harmless, but the code would silently regress when iOS behavior changes.
    
    Consider guarding `focusKeyboardInput` with an in-flight flag or removing the now-redundant `onTouchStart` listener, since `onPointerDown` with `pointerType !== "mouse"` already covers touch inputs.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/web/src/components/MobileTerminalInput/MobileTerminalInput.tsx:67-88
**Double `focusKeyboardInput` call creates conflicting restore cycles**

Both `onTouchStart` and the non-mouse branch of `onPointerDown` call `focusKeyboardInput`. On iOS touch, both events fire for the same gesture — `touchstart` fires before `pointerdown` — so two independent save/restore cycles are scheduled. If iOS's keyboard-avoidance scroll fires synchronously during the first `focus()` call, the second call captures the post-scroll (displaced) position as its `savedScrollTop` and its rAF chain restores the page to that displaced position, after the first chain has already corrected it. In async-scroll scenarios this is harmless, but the code would silently regress when iOS behavior changes.

Consider guarding `focusKeyboardInput` with an in-flight flag or removing the now-redundant `onTouchStart` listener, since `onPointerDown` with `pointerType !== "mouse"` already covers touch inputs.

### Issue 2 of 2
apps/web/src/app/workspaces/page.tsx:26-39
**`formatRelative` timestamps freeze at the time of initial render**

`Date.now()` is evaluated once per call, so the "created Nd ago" labels are accurate at load time but never refresh while the user stays on the page. The `createdWithin` cutoff in `visibleWorkspaces` has the same characteristic — it is recomputed only when a dependency changes, not as wall-clock time advances. If auto-updating labels are not a requirement, a short comment noting this intentional behaviour would prevent future confusion.

Reviews (1): Last reviewed commit: "fix(web): stop terminal focus from scrol..." | Re-trigger Greptile

Comment on lines +26 to 39
function formatRelative(date: Date): string {
const diffMs = Date.now() - date.getTime();
const minutes = Math.round(diffMs / 60_000);
if (minutes < 1) return "just now";
if (minutes < 60) return `${minutes}m ago`;
const hours = Math.round(minutes / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.round(hours / 24);
if (days < 30) return `${days}d ago`;
const months = Math.round(days / 30);
if (months < 12) return `${months}mo ago`;
const years = Math.round(months / 12);
return `${years}y ago`;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 formatRelative timestamps freeze at the time of initial render

Date.now() is evaluated once per call, so the "created Nd ago" labels are accurate at load time but never refresh while the user stays on the page. The createdWithin cutoff in visibleWorkspaces has the same characteristic — it is recomputed only when a dependency changes, not as wall-clock time advances. If auto-updating labels are not a requirement, a short comment noting this intentional behaviour would prevent future confusion.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/src/app/workspaces/page.tsx
Line: 26-39

Comment:
**`formatRelative` timestamps freeze at the time of initial render**

`Date.now()` is evaluated once per call, so the "created Nd ago" labels are accurate at load time but never refresh while the user stays on the page. The `createdWithin` cutoff in `visibleWorkspaces` has the same characteristic — it is recomputed only when a dependency changes, not as wall-clock time advances. If auto-updating labels are not a requirement, a short comment noting this intentional behaviour would prevent future confusion.

How can I resolve this? If you propose a fix, please make it concise.

Math.round on the unit conversions caused boundaries like 23.5h to
display as "1d ago" (rounded to 24h, failed the <24 check, then
floored at the day step). Switching to Math.floor keeps each unit on
its own scale until the next whole unit has actually elapsed.
@AviPeltz AviPeltz merged commit af606c7 into main May 17, 2026
17 checks passed
sazabi Bot pushed a commit that referenced this pull request May 20, 2026
* feat(web): sort and filter workspaces by created date

Adds a "Recently created / Oldest / Name" sort and a "created within
7/30/90 days" filter to the /workspaces list, and surfaces a relative
"created Nd ago" label on each row. v2Workspace.list now returns
createdAt so the client can sort/filter without an extra round-trip.

* fix(web): stop terminal focus from scrolling page to top on mobile

The hidden input used to capture mobile keystrokes sat at the bottom of
the layout viewport. On iOS Safari, focusing it triggered the keyboard-
avoidance scroll, which on a viewport-height page yanked the whole
terminal to the top.

Move the input above the keyboard with pointer-events-none, and
defensively restore the scroll position across the next two animation
frames in case the OS scroll fires after focus.

* fix(web): use Math.floor in formatRelative to avoid premature rollovers

Math.round on the unit conversions caused boundaries like 23.5h to
display as "1d ago" (rounded to 24h, failed the <24 check, then
floored at the day step). Switching to Math.floor keeps each unit on
its own scale until the next whole unit has actually elapsed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant