Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh

Commit

Getting started works on a phone: at 390px the sheet hid the step rail and offered only Finish once every step was done, so a finished workspace (flagon-io) could not reach any other step; the sheet changed height on every Next (447 to 836px); a step opened at the previous step's scroll position (on a computer too); and its buttons, tabs and copy buttons were 24 to 32px. A phone now has a strip of six segments under the workspace's name and the step's place, each tapped to go to its step (green done, lavender here, dim skipped); once all is done, Next sits beside Finish to look back over the steps (on a computer too); the split sheet is one height (44rem, less while the keyboard is up) with the pane scrolling inside it; each step starts at its top; on a touch screen the step's small buttons grow to 40 and 36px, tabs to 32px and CopyLine's copy button to 34px; the sheet takes focus itself on open rather than the first segment; numbered instructions run full width under their number; a provider's line wraps rather than cuts its key hint. Checked: rendered with fresh, part-done and all-done workspaces at 320, 360, 390 and 1280px (no sideways overflow, every step reachable); apps/web tsc -b --force shows no errors in the changed files.

G1t-Session:
ses_01m4z4mcw5ev0r3vkcnaz5hrmk
syntaqxcommitted Parentb2c7528Browse files
3 files+85−150/3 viewed
+80−12
3535
3636 import { AI_PROVIDER_GROUPS, AI_PROVIDER_LOOKS, aiProvidersIn } from "../lib/ai-providers";
3737 import { autoOffered, defaultModel, modelOptions } from "../lib/ask-model";
38+import { cn } from "../lib/cn";
3839 import {
3940 type OnboardingStepView,
4041 type OnboardingView,
154155
155156 return (
156157 <Dialog open={open} onOpenChange={(next) => (next ? onOpenChange(true) : close())}>
157− <DialogContent size="split" aria-describedby="onboarding-about">
158+ <DialogContent
159+ size="split"
160+ aria-describedby="onboarding-about"
161+ onOpenAutoFocus={(event) => {
162+ // On a phone the sheet itself takes focus, not the first step's
163+ // segment (a ring for no reason) or a field (the keyboard).
164+ if (!matchMedia("(max-width: 639px)").matches) return;
165+ event.preventDefault();
166+ (event.currentTarget as HTMLElement).focus();
167+ }}
168+ >
158169 {view ? (
159170 <Guide view={view} step={current} onStep={setStep} onFinish={close} />
160171 ) : (
240251 </p>
241252 </aside>
242253
243− {/* The step at hand. */}
244− <section className="flex min-h-0 flex-col">
245− <div className="space-y-3 px-4 pt-5 sm:hidden">
246− <p className="text-xs text-muted tabular-nums">
247− Getting started · Step {index + 1} of {steps.length}
248− </p>
249− <Progress value={progress.percent} aria-label="Setup progress" indicatorClassName={done ? "bg-success" : undefined} />
250− </div>
251− <div className="min-h-0 grow overflow-y-auto overscroll-contain p-6 max-sm:px-4 max-sm:pt-4">
254+ {/* The step at hand. On a touch screen its buttons are a finger's
255+ size (`h-8` and `h-7` ones grow to 40 and 36 pixels). */}
256+ <section className="flex min-h-0 flex-col pointer-coarse:[&_[data-slot=button].h-7]:h-9 pointer-coarse:[&_[data-slot=button].h-8]:h-10 pointer-coarse:**:[[role=tab]]:py-2">
257+ <StepStrip view={view} index={index} onStep={onStep} done={done} />
258+ {/* Keyed by step, so each one starts at its top. */}
259+ <div key={step} className="min-h-0 grow overflow-y-auto overscroll-contain p-6 max-sm:px-4 max-sm:pt-5">
252260 {steps.map((s) => (
253261 <TabsContent key={s.key} value={s.key} className="mt-0 space-y-5">
254262 <header className="space-y-2 pr-8">
284292 );
285293 }
286294
295+/**
296+ * A phone's rail: where the guide stands, as one segment per step to tap
297+ * to go to it (green done, lavender here, dimmed skipped), under the
298+ * workspace's name and the step's place.
299+ */
300+function StepStrip({
301+ view,
302+ index,
303+ onStep,
304+ done,
305+}: {
306+ view: OnboardingView;
307+ index: number;
308+ onStep: (step: OnboardingStep) => void;
309+ done: boolean;
310+}) {
311+ const { steps } = view;
312+ const progress = progressOf(steps);
313+ return (
314+ <nav aria-label="Steps" className="border-b border-line px-4 pt-4 pr-14 sm:hidden">
315+ <p className="flex min-w-0 items-baseline gap-1.5 text-xs text-muted tabular-nums">
316+ <span className="truncate font-medium text-fg">{view.name}</span>
317+ <span aria-hidden="true">·</span>
318+ <span className="shrink-0">
319+ {done ? "All set" : `Step ${index + 1} of ${steps.length} · ${progress.settled} done`}
320+ </span>
321+ </p>
322+ <ol className="-mx-0.5 flex">
323+ {steps.map((s, i) => (
324+ <li key={s.key} className="min-w-0 grow">
325+ <button
326+ type="button"
327+ onClick={() => onStep(s.key)}
328+ aria-current={i === index ? "step" : undefined}
329+ aria-label={`${s.title}: ${s.done ? "done" : s.skipped ? "skipped" : "to do"}`}
330+ className="group flex h-10 w-full items-center px-0.5 outline-none"
331+ >
332+ <span
333+ className={cn(
334+ "h-1.5 w-full rounded-full transition-[background-color,height] group-focus-visible:ring-2 group-focus-visible:ring-accent/50",
335+ s.done ? "bg-success" : s.skipped ? "bg-line" : "bg-line-strong",
336+ i === index && (s.done ? "h-2" : "h-2 bg-accent"),
337+ i !== index && s.done && "opacity-60",
338+ )}
339+ />
340+ </button>
341+ </li>
342+ ))}
343+ </ol>
344+ </nav>
345+ );
346+}
347+
287348 /** Skip, back and next; Finish when there is nothing left, or on the last step. */
288349 function Footer({
289350 view,
336397 Back
337398 </Button>
338399 )}
400+ {/* All done, the steps are still there to look back over. */}
401+ {done && !last && (
402+ <Button variant="outline" size="sm" onClick={next}>
403+ Next
404+ <ArrowRight size={14} />
405+ </Button>
406+ )}
339407 {done || last ? (
340408 <Button variant="accent" size="sm" onClick={onFinish}>
341409 {done ? "Finish" : "Done for now"}
425493 <AiProviderMark provider={connection.provider} size={28} />
426494 <div className="min-w-0 grow">
427495 <p className="truncate text-sm font-medium">{connection.name}</p>
428− <p className="truncate text-xs text-muted">
496+ <p className="text-xs text-muted sm:truncate">
429497 {[AI_PROVIDERS[connection.provider].label, connection.models.length > 0 && `${connection.models.length} models`, connection.secretHint && `key ${connection.secretHint}`]
430498 .filter(Boolean)
431499 .join(" · ")}
697765 <li className="grid grid-cols-[1.5rem_minmax(0,1fr)] gap-x-3 gap-y-2">
698766 <Badge shape="mark" className="tabular-nums">{n}</Badge>
699767 <p className="self-center text-sm font-medium">{title}</p>
700− <div className="col-start-2 space-y-2">{children}</div>
768+ <div className="col-start-2 space-y-2 max-sm:col-span-2 max-sm:col-start-1">{children}</div>
701769 </li>
702770 );
703771 }
+4−2
3838 * `split`: a wide panel with no padding of its own, for a dialog in two
3939 * columns (a rail and a pane, as Getting started is) whose parts scroll on
4040 * their own. Its height is fixed on a computer so moving between steps
41− * does not make it jump; on a phone it is the sheet, as tall as it needs.
41+ * does not make it jump. On a phone it is the sheet, of one height for
42+ * the same reason (up to 44rem, less while the keyboard is up), and its
43+ * pane scrolls inside it.
4244 */
4345 const SIZES = {
4446 default: "",
4547 split:
46− "max-w-4xl gap-0 p-0 sm:h-[min(42rem,calc(100dvh-4rem))] sm:grid-cols-[16rem_minmax(0,1fr)] sm:grid-rows-[minmax(0,1fr)] sm:overflow-hidden max-sm:grid-cols-[minmax(0,1fr)] max-sm:pb-[env(safe-area-inset-bottom)]",
48+ "max-w-4xl gap-0 p-0 sm:h-[min(42rem,calc(100dvh-4rem))] sm:grid-cols-[16rem_minmax(0,1fr)] sm:grid-rows-[minmax(0,1fr)] sm:overflow-hidden max-sm:h-[44rem] max-sm:grid-cols-[minmax(0,1fr)] max-sm:grid-rows-[minmax(0,1fr)] max-sm:overflow-hidden max-sm:pb-[env(safe-area-inset-bottom)]",
4749 } as const;
4850
4951 export function DialogContent({
+1−1
289289 disabled={disabled}
290290 variant="ghost"
291291 size="inline"
292− className="p-1.5 text-faint"
292+ className="p-1.5 text-faint pointer-coarse:p-2.5"
293293 onClick={() => {
294294 void navigator.clipboard.writeText(text);
295295 setCopied(true);

Comments

Sign in to comment.