mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(tg): mini-app responsiveness — safe-area activation, truncation, touch targets (#647)
Root cause first: no viewport export existed anywhere, so viewport-fit was never 'cover' and every env(safe-area-inset-*) resolved to 0 on notched iPhones — content under the status bar, dock without real home-indicator clearance. The export lives on the (tg) group layout (server component), NOT app-wide: the dashboard shell has no safe-area padding and must not inherit cover. Also: min-w-0 on four truncating flex children that overflowed their justify-between rows (chat names/previews, fleet task titles); object-contain on the approvals video (letterbox instead of distort on short phones); break-words on the changelog pre / task description / quoted mention; touch targets bumped to >=36px (sheet close, segmented controls, ack button, chips, back button, bell, jump-to-latest, cut-toggle); overflow-x-hidden backstop on the (tg) main scroller; fleet avatar strip sliced to 3 with a +N badge instead of silent clipping. Verified: pnpm typecheck clean, lint 0 errors, panel suite 870/870. Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,17 @@
|
||||
import type { Viewport } from "next";
|
||||
import Script from "next/script";
|
||||
import localFont from "next/font/local";
|
||||
|
||||
// Scoped to the (tg) group: viewport-fit=cover is what makes the
|
||||
// env(safe-area-inset-*) paddings below resolve to real values on notched
|
||||
// phones. The dashboard shell has no safe-area handling, so it must NOT
|
||||
// inherit cover (content would sit under the sensor housing in landscape).
|
||||
export const viewport: Viewport = {
|
||||
width: "device-width",
|
||||
initialScale: 1,
|
||||
viewportFit: "cover",
|
||||
};
|
||||
|
||||
// The brand display face — the same vendored Share Tech Mono the motion/
|
||||
// video compositions use for their headline moments. Exposed as a CSS
|
||||
// variable on the shell so `.tg-display` (globals.css) can reach it; body
|
||||
@@ -45,7 +56,7 @@ export default function TelegramLayout({
|
||||
src="https://telegram.org/js/telegram-web-app.js"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
<main className="flex-1 overflow-auto pt-[env(safe-area-inset-top)]">
|
||||
<main className="flex-1 overflow-x-hidden overflow-y-auto pt-[env(safe-area-inset-top)]">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -187,7 +187,7 @@ function CockpitShell() {
|
||||
aria-label={unread > 0 ? `Inbox, ${unread} unread` : "Inbox"}
|
||||
onClick={() => setInboxOpen(true)}
|
||||
className={cn(
|
||||
"relative flex h-9 w-9 items-center justify-center rounded-full bg-card text-muted-foreground",
|
||||
"relative flex h-10 w-10 items-center justify-center rounded-full bg-card text-muted-foreground",
|
||||
TG_PRESS,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -110,7 +110,7 @@ export function ReleaseDetail({
|
||||
)}
|
||||
|
||||
<TgSection title="Changelog draft">
|
||||
<pre className="whitespace-pre-wrap rounded-xl bg-muted p-2 text-xs leading-relaxed">
|
||||
<pre className="whitespace-pre-wrap break-words rounded-xl bg-muted p-2 text-xs leading-relaxed">
|
||||
{report.drafted_changelog}
|
||||
</pre>
|
||||
</TgSection>
|
||||
|
||||
@@ -72,7 +72,7 @@ function CutPlayer({ post }: { post: VideoPost }) {
|
||||
{(Object.keys(CUT_LABELS) as VideoCut[]).map((c) => (
|
||||
<Button
|
||||
key={c}
|
||||
size="sm"
|
||||
size="default"
|
||||
variant={cut === c ? "default" : "outline"}
|
||||
disabled={!paths[c]}
|
||||
onClick={() => setCut(c)}
|
||||
@@ -88,7 +88,7 @@ function CutPlayer({ post }: { post: VideoPost }) {
|
||||
controls
|
||||
playsInline
|
||||
className={cn(
|
||||
"w-full rounded-md bg-black",
|
||||
"w-full rounded-md bg-black object-contain",
|
||||
cut === "vertical"
|
||||
? "aspect-[9/16] max-h-[60dvh]"
|
||||
: "aspect-square",
|
||||
|
||||
@@ -81,7 +81,7 @@ export function XPostDetail({
|
||||
</div>
|
||||
|
||||
{post.mention && (
|
||||
<blockquote className="border-l-2 pl-2 text-xs text-muted-foreground">
|
||||
<blockquote className="break-words border-l-2 pl-2 text-xs text-muted-foreground">
|
||||
{post.mention.text}
|
||||
</blockquote>
|
||||
)}
|
||||
|
||||
@@ -89,7 +89,7 @@ export function TgSheet({
|
||||
type="button"
|
||||
aria-label="Close"
|
||||
onClick={onClose}
|
||||
className="flex h-7 w-7 items-center justify-center rounded-full bg-muted text-muted-foreground transition-transform active:scale-90"
|
||||
className="flex h-9 w-9 items-center justify-center rounded-full bg-muted text-muted-foreground transition-transform active:scale-90"
|
||||
>
|
||||
<X weight="bold" className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
@@ -254,7 +254,7 @@ function SecretaryView({ onBack }: { onBack: () => void }) {
|
||||
haptics.tap();
|
||||
void stop().then(onBack);
|
||||
}}
|
||||
className="rounded-full bg-muted/60 px-3 py-1.5 text-xs font-medium text-muted-foreground"
|
||||
className="rounded-full bg-muted/60 px-3 py-2 text-xs font-medium text-muted-foreground"
|
||||
>
|
||||
End
|
||||
</button>
|
||||
@@ -399,7 +399,7 @@ function MineList({
|
||||
<TgAvatar name={c.other_agent} />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-baseline justify-between gap-2">
|
||||
<span className="truncate text-[15px] font-medium">
|
||||
<span className="min-w-0 truncate text-[15px] font-medium">
|
||||
{getAgentDisplayName(c.other_agent)}
|
||||
</span>
|
||||
{c.last_message_at && (
|
||||
@@ -411,7 +411,7 @@ function MineList({
|
||||
<div className="mt-0.5 flex items-center justify-between gap-2">
|
||||
<p
|
||||
className={cn(
|
||||
"truncate text-[13px] leading-snug",
|
||||
"min-w-0 truncate text-[13px] leading-snug",
|
||||
c.unread_count > 0
|
||||
? "font-medium text-foreground/80"
|
||||
: "text-muted-foreground",
|
||||
@@ -478,7 +478,7 @@ function FleetList({
|
||||
<PairAvatars a={c.agent_a} b={c.agent_b} />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-baseline justify-between gap-2">
|
||||
<span className="truncate text-[15px] font-medium">
|
||||
<span className="min-w-0 truncate text-[15px] font-medium">
|
||||
{getAgentDisplayName(c.agent_a)}
|
||||
<span className="mx-1 text-muted-foreground/50">↔</span>
|
||||
{getAgentDisplayName(c.agent_b)}
|
||||
@@ -838,7 +838,7 @@ function ThreadView({
|
||||
setShowJump(false);
|
||||
}}
|
||||
className={cn(
|
||||
"absolute bottom-2 right-2 flex h-9 w-9 items-center justify-center rounded-full bg-card text-primary shadow-lg ring-1 ring-white/[0.08]",
|
||||
"absolute bottom-2 right-2 flex h-10 w-10 items-center justify-center rounded-full bg-card text-primary shadow-lg ring-1 ring-white/[0.08]",
|
||||
TG_PRESS,
|
||||
)}
|
||||
>
|
||||
@@ -863,7 +863,7 @@ function ThreadView({
|
||||
fleetTarget === threadRef.a ? threadRef.b : threadRef.a,
|
||||
)
|
||||
}
|
||||
className="mt-2 inline-flex items-center gap-1 rounded-full bg-muted/60 px-3 py-1 text-xs font-medium text-muted-foreground"
|
||||
className="mt-2 inline-flex items-center gap-1 rounded-full bg-muted/60 px-3 py-2 text-xs font-medium text-muted-foreground"
|
||||
>
|
||||
To: {getAgentDisplayName(fleetTarget)}
|
||||
<span className="text-muted-foreground/50">· tap to switch</span>
|
||||
|
||||
@@ -145,7 +145,7 @@ function NotificationRow({
|
||||
type="button"
|
||||
disabled={ackPending}
|
||||
onClick={onAck}
|
||||
className="flex h-7 shrink-0 items-center gap-1 rounded-full bg-primary px-2.5 text-xs font-semibold text-primary-foreground disabled:opacity-60"
|
||||
className="flex h-9 shrink-0 items-center gap-1 rounded-full bg-primary px-2.5 text-xs font-semibold text-primary-foreground disabled:opacity-60"
|
||||
>
|
||||
<Check className="h-3.5 w-3.5" />
|
||||
Ack
|
||||
|
||||
@@ -268,7 +268,7 @@ export function TgTaskSheet({
|
||||
</div>
|
||||
|
||||
{task.description && (
|
||||
<p className="whitespace-pre-line text-sm leading-relaxed text-muted-foreground">
|
||||
<p className="whitespace-pre-line break-words text-sm leading-relaxed text-muted-foreground">
|
||||
{task.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -198,7 +198,7 @@ function NeedsYouBanner({
|
||||
key={key}
|
||||
type="button"
|
||||
onClick={onApprovals}
|
||||
className="rounded-full bg-violet-500/15 px-2.5 py-1 text-xs font-medium tabular-nums text-violet-300 transition-colors active:bg-violet-500/25"
|
||||
className="rounded-full bg-violet-500/15 px-2.5 py-2 text-xs font-medium tabular-nums text-violet-300 transition-colors active:bg-violet-500/25"
|
||||
>
|
||||
{DRAFT_LABELS[key] ?? key} · {count}
|
||||
</button>
|
||||
@@ -474,9 +474,14 @@ export function TgTodayTab({
|
||||
className="w-full space-y-2 text-left"
|
||||
>
|
||||
<div className="flex gap-2 overflow-hidden">
|
||||
{fleet.working.map((a) => (
|
||||
{fleet.working.slice(0, 3).map((a) => (
|
||||
<TgAvatar key={a.name} name={a.name} active />
|
||||
))}
|
||||
{fleet.working.length > 3 && (
|
||||
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-muted/70 text-[10px] font-semibold text-muted-foreground">
|
||||
+{fleet.working.length - 3}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<ul className="space-y-1">
|
||||
{fleet.working.slice(0, 3).map((agent) => (
|
||||
@@ -488,7 +493,7 @@ export function TgTodayTab({
|
||||
{agent.name}
|
||||
</span>
|
||||
{agent.task_title && (
|
||||
<span className="truncate text-muted-foreground">
|
||||
<span className="min-w-0 truncate text-muted-foreground">
|
||||
{agent.task_title}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -344,7 +344,7 @@ export function TgSegmented<T extends string>({
|
||||
onChange(o.value);
|
||||
}}
|
||||
className={cn(
|
||||
"relative z-10 rounded-full py-1.5 text-center text-[13px] font-medium transition-colors duration-200",
|
||||
"relative z-10 rounded-full py-2 text-center text-[13px] font-medium transition-colors duration-200",
|
||||
o.value === value ? "text-foreground" : "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
@@ -385,7 +385,7 @@ export function TgSubPage({
|
||||
aria-label="Back"
|
||||
onClick={onBack}
|
||||
className={cn(
|
||||
"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-card text-muted-foreground",
|
||||
"flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-card text-muted-foreground",
|
||||
TG_PRESS,
|
||||
)}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user