Commit Graph
150 Commits
Author SHA1 Message Date
ZacharyZhang-NY 207d4b0109 Project · Plans: a roadmap, a burndown, milestones, workload
Roadmap lays initiatives across months in lanes: each a bar from its
first day through its last, filled as far as it has come, under the
months' names and a line at today; hover a bar for its dates. Bars sit
at fractions of the span, so nothing is measured, and a strip too
narrow for its months scrolls sideways at a least width per month.
GanttChart and TimelineView are the charts GanttChart.

BurndownChart lives with the charts, on their scales, labels, tiles
and paint: the ideal line from the scope down to nothing, what was left
at each day's end so far, and a line at today; hover a day to read it.

MilestoneProgress shows a milestone's issues done as a bar and a count,
when it is due, and whether it keeps pace: on track, at risk when the
work trails the time gone by more than a tenth, late past its day, or
done. WorkloadView sets each person's points against what they can
take, the bar turning red past it, in columns that line up.
2026-09-27 05:42:03 -04:00
ZacharyZhang-NY e630e61a63 Project · a focused card scrolls into view
The board scrolled sideways without a handle, so a card that took
focus past the fold stayed out of sight, ring and all, and Option-Right
could send it into a column nobody saw. The board now tracks its
scroll, and a card that takes focus sets the offset through
layout::bring_into_view from its row's painted box, ring included, once
per focus and again when it lands in another column. The stepping
rules gain tests for left and right keeping a card's place and for the
end of a shorter column.
2026-09-27 05:28:35 -04:00
ZacharyZhang-NY 8cbd389d72 Project · Issues and boards: a board, issue cards, a task's panel, labels
KanbanBoard lays columns of cards side by side. A card drags to a
place in any column: it rides raised at the pointer, a slot opens where
it would land, the other cards glide aside on motion::Flip, and the
drop asks the owner through on_move(card, column, index); a drop in its
own place asks nothing. Option with an arrow moves the focused card,
whose focus the board keys by card so it follows it across columns. A
press, Enter or Space opens it. Sprint Board is this board.

IssueCard shows an issue's status, name, assignee, title, priority,
labels and due day. TaskDetailPanel lays a task out in full: its title
edits in place, its status, priority, assignee and due day change where
they stand, and its notes and steps follow; each edit hands the whole
task on, and a blank title is not taken. LabelManager tends a team's
labels: a hue picked from a popover, a name edited in place, a delete,
and a field that makes a new one; names stay unique in any case, and a
refused name says why.

The fill rule changes: min_w_full, taken up in T30d, resolves against
a block's outer width in taffy 0.9, so a field in a padded card ran
past its padding. A component that fills now takes no width on its
root, which blocks and flex columns stretch; FormField and the new
roots do. The chart's hue names move to theme::HUE_NAMES, which the
event editor and the label manager share.
2026-09-27 05:12:20 -04:00
ZacharyZhang-NY 1633bc9293 Project · Tasks: priorities, issues, statuses, assignees, task rows
A new chapter, src/project. PriorityIndicator shows how urgent a task
is in three bars, urgent in red, its words in a tip or beside it.
IssueIdBadge names an issue by its team's key and number, as ENG-123.
StatusSelect picks a status from the workflow, each with its mark, and
AssigneePicker picks a person or no one; DueDatePicker is the forms
DatePicker. TaskItem is a task in a row: its box, priority and issue,
its title, then its due day, red when past and still open, and who has
it. TaskList puts them in a SelectableList: arrows move, Enter opens,
and a box's press or keys stay at the box. SubtaskList is the
documents Checklist, which gains a field that adds a step on Enter.

Along the way: forms::Choice::avatar leads a person's row with their
initials, in lists and in a Select's trigger; ListItem::struck strikes
a closed task's title; typography::Ellipsis now shapes with the text
style's run, so a strike or underline on its box shows; two Lucide
icons, circle-dashed and circle-dot-dashed.
2026-09-27 04:48:54 -04:00
ZacharyZhang-NY c99465153d Calendar · open times start over on new days
AvailabilityPicker kept the day chosen in keyed state whatever days the
owner passed next, and the slots lookup expected it among them: an
owner paging to the next week crashed the app once a day was chosen.
The choice is now seeded on the owner's days and pick, so a new run of
days starts it over at the picked day or the first open one.
2026-09-27 04:31:14 -04:00
ZacharyZhang-NY 0bcfd82bf3 Calendar · Editing: an event's editor, its repeats, reminders and open times
EventEditor puts an event's parts in a form: its title, whole days or
hours, where, its color, how it repeats, its reminders and notes. Save
waits for a title and an end after the start and hands on an
EventDraft; Cancel and Delete show when the owner handles them. A new
draft from the owner starts the editor over.

RecurrenceEditor sets a rule after RFC 5545: daily, weekly on its days,
monthly or yearly, every so many, until a day or a count. A line reads
the rule back with the next days it falls on. ReminderPicker keeps
reminders from a fixed list, soonest first, and Add brings the nearest
one not yet set. AvailabilityPicker counts the open slots of each day
and lists the chosen day's, around what is busy, inside working hours
and after now.

FormField filled its column with a percent width, which taffy 0.9
leaves at content width when a block measures the column first and the
width comes out the same: at 280px the editor's fields shrank to their
labels. It now fills with min_w_full, as new roots do; older roots move
over in T44.
2026-09-27 04:22:02 -04:00
ZacharyZhang-NY 48ff348357 Calendar · Agenda and people: an agenda, an event's details, its guests
AgendaView lists the days ahead under their names, whole days first,
then by the hour, and leaves out days with nothing; it rides on
lists::Sections, so a press or an arrow selects and Enter opens. An
empty stretch says nothing is planned.

EventPopover is an event's chip that opens its details: its title in
its hue, when and where, who is asked, and Edit and Delete when the
owner handles them. It opens through Popover::with_opener, which now
hands any element the toggle and keeps focus, placement and every way
out in the popover. AttendeeList counts the answers and lists each
guest with their answer, the organizer first. A hue worn in a row is
now data_display::color_mark, for mail labels and events alike.
2026-09-27 03:35:59 -04:00
ZacharyZhang-NY 57dd55320e Calendar · views on the clock keep moving
The week and day views read the clock once and handed that moment down
as given, which turned off the line's ticker: the line at now and the
lit day stood still. The owner's moment now goes down unchanged, and a
time grid, month view or year view on the clock keeps its own ticker,
so the line moves and today turns over at midnight, even on days that
are not today. A given moment holds them still.
2026-09-27 03:23:18 -04:00
ZacharyZhang-NY 7e3f99f278 Calendar · Time grid: week and day views, drag to make and to resize
TimeGrid sets days side by side under a column of hours. Each event is
a card by its hours, clipped to each day it touches, and events that
overlap share the width in lanes. A drag on empty time makes an event
by quarter hours through forms::pen; a drag on an event's lower edge,
on the day it ends, moves its end, each drag checked against its
owner. The grid opens at half past seven, or an hour and a half
before now when today is shown and that is later.

CurrentTimeIndicator marks the minute now on today and moves with the
clock. TimezoneOverlay adds another zone's hours beside the grid's.
AllDayRow lays whole days across their columns in lanes.
CalendarWeekView and CalendarDayView are one view of seven days or
one: a header that steps and comes back to today, the days' names
stacked so they fit narrow columns, the whole days, then the hours.
2026-09-27 03:13:38 -04:00
ZacharyZhang-NY cd5ed088c7 Calendar · Months: a month view, a year view, event chips and cards
CalendarMonthView lays a month in week rows. An event across days lies
over them as a bar in the first free lane, cut at the week's edge;
the rest sit by the hour as chips, and a crowded day keeps the rest
behind "N more". Arrows move a day, Page keys turn the month, and
Enter or a double press opens the day; the cursor holds through the
owner's echo of a turn. Days sit at sevenths of their row, as the bars
do, so text never widens a column.

YearView shows twelve small months; a day with something on it wears a
dot in its first event's hue. EventChip is an event in a line and
EventCard a block with its hours and place. MiniCalendar points to
forms::Calendar. Palette::hue is now the one home for a chart color by
index, for mail labels and events alike.
2026-09-27 02:49:37 -04:00
ZacharyZhang-NY 51ade29032 Primitives · a raise keeps its state under its owner's id
A raise inside another was namespaced by its place in the layer's
queue, so one that came before it moved its id path and rebuilt its
keyed state and focus: an open date popup would lose its month when a
tip appeared. raise now takes its owner's id and draws under it, and
float and float_height pass the owner's id on from every call site.
A test holds a focused box in a raise while another comes before it.
2026-09-27 02:23:05 -04:00
ZacharyZhang-NY 341420d1e4 Primitives · raise: what opens inside a dialog lies over it
gpui 0.2.2 asserts on a deferred draw inside another, and every dialog,
sheet and popover draws inside one, so opening a date field in the
snooze or schedule dialog panicked, as would any list, menu or picker
opened in an overlay. primitives::raise replaces each deferred draw:
at the top it defers as before; inside something raised it leaves a
placeholder in the tree, and its layer lays it out on its own, then
draws it last. check.sh refuses gpui's deferred anywhere else.

The time dialog's rule is now times::ahead, tested for a moment
behind, now itself, a moment ahead and another zone; a gpui test picks
yesterday in the snooze dialog, sees that it has passed and that
nothing is sent, then picks two days ahead and hears that moment.
2026-09-27 01:51:51 -04:00
ZacharyZhang-NY ccbc0970e5 Mail · Sorting: a label picker and a snooze menu
LabelPicker puts labels on a message: a field finds them by name,
arrows move and a press or Enter turns one on or off, each row with its
check and its hue. A name no label has can be made into one, and the
field starts over. Its rows are the option rows every list shares;
forms::marked_row leads one with a mark. The cursor shows only while
the field has focus.

SnoozePicker offers later today, tomorrow, this weekend and next week,
each with its day and hour, or a time picked in a dialog. It shares
mail::times with ScheduleSend: the names, the dialog, which now waits
for a time ahead of now, and a rule that a moment comes once, so on a
Sunday tomorrow morning no longer repeats as Monday morning.
2026-09-27 01:16:24 -04:00
ZacharyZhang-NY ccd48f9ba2 Mail · Writing: the composer, recipients that suggest, a signature, send later
MailComposer writes a message: To, and Cc and Bcc once asked for, as
tag fields that suggest contacts and mark what is not an address; the
subject and the words; the files, each with a way off; the signature
under the words. Send wakes once there is someone to send to and every
address is one; it hands over the draft now or at a time, and Discard
lets it go. A new start from the owner begins the draft over.

forms::TagInput now suggests: the rows that hold the typed words float
under it through the list Combobox uses; Up, Down and Enter pick, and
Escape sets them aside. RecipientInput points to it. ScheduleSend
offers tomorrow morning, tomorrow afternoon, Monday morning, or a day
and hour picked in a dialog. SignatureEditor writes the lines and shows
how they sit under a message; it tells the owner's echo from a new
signature, so the caret stays.
2026-09-27 00:27:23 -04:00
ZacharyZhang-NY 70e18af092 Mail · Reading: a message reader, a conversation, and quoted text
MailReader opens a message under its subject: the sender's picture,
name and address, who it went to, when it came, and Reply, Reply all
when it went to more than one, and Forward, each only with a handler;
then its words and its files, each downloadable. MailThreadView lays a
conversation oldest first: the newest stands open, the others fold to
a line of the sender, their first words and when, which a press, Enter
or Space opens. QuotedText folds words quoted from before behind a
small mark; open, they sit behind a rule. Long names give way at the
label width.
2026-09-26 23:57:06 -04:00
ZacharyZhang-NY 7d20567921 Mail · Boxes and lists: mailboxes, folders and labels, the mail list, inbox zero
MailboxList sets mailboxes, folders and labels in sections: a press, an
arrow or Enter opens one, the open one lit; a box with mail unread
stands out with its count, and a label wears one of the theme's chart
hues. MailList holds messages in a multiple-select list: the sender
strong beside a dot while unread, the subject and first words under
it, the time today, the weekday this week, the date this year or with
its year, a paperclip for files, and a star whose press stays off the
row. InboxZeroState points to feedback::EmptyState.

Sections of selectable lists now have one home, lists::Sections, which
ChannelList and MemberList use too; ListItem gains a third line.
2026-09-26 23:35:04 -04:00
ZacharyZhang-NY 1700a72753 Messaging · Stickers and GIFs: a sticker picker and a GIF picker
StickerPicker shows stickers a pack at a time, chosen above with a
segmented control; a search spans every pack and keeps the stickers
whose names hold it. Arrows move, Enter or a press picks. It reuses the
glyph grid, now forms::picks, which takes its columns and cell size and
titles from the host, so emoji, icons and stickers share one searchable
grid; forms::glyphs keeps the emoji and icon pickers.

GifPicker packs host GIFs in two columns, each moving, under a search
it owns; the host hears the trimmed words only when they change, and
categories fill the search while it is empty. Waiting, it shows tiles
in GIF shapes; finding nothing, it says so. Each GIF is a Tab stop that
a press, Enter or Space picks.

The share toggle now shows its state, as the microphone and camera do.
The gallery ships line-drawn stickers and small GIFs made from its own
pictures.
2026-09-26 23:00:15 -04:00
ZacharyZhang-NY dd465fedb7 Messaging · Calls: controls, tiles, a grid, a shared screen, a bar, a ringing dialog, a huddle
CallControls holds the microphone, camera and a shared screen as
toggles, then Leave. ParticipantTile shows a camera's frame from the
host or the person's initials, their name with a mark when muted, and a
ring while they speak. VideoCallGrid lays tiles in even rows as far as
they fit across its measured width; a short last row centers at the
full rows' width. ScreenShareView names who presents and offers Stop
presenting for your own share, the call's tiles beside or below.
VoiceCallBar tells where a call is, how long it has run and who is in
it, with the microphone and Leave. IncomingCallDialog rings with the
caller's picture; Accept or Decline answers each ring once, and Escape
declines. HuddleIndicator rings while a huddle is live; a press joins.

The recorder's device toggle and the camera's name chip now have one
home in media. People: member names sort whatever their case, a
status's words save trimmed, and a profile card's local time ticks.
2026-09-26 22:33:54 -04:00
ZacharyZhang-NY dd5e36cd94 Messaging · People: members, a profile card, online status and a status setter
MemberList sets members online and offline by name, each group a list
under its count. A press or an arrow chooses one; Enter or a double
press acts on them; offline members go quiet. UserProfileCard shows a
person's picture with presence, name and standing, title, status, the
time in their zone, and Message and Call, each only with a handler.
OnlineStatus draws a presence's dot and word; Presence now names itself
and gives its color, in data_display, its one home.

StatusSetter holds a draft seeded from the owner's status: an emoji
from a picker field, words in its own text field, and when they clear.
Suggestions fill all three; Save hands the status over and Clear status
removes it. The emoji field reuses the date pickers' field, whose
trigger ignores the Enter release after a pick.
2026-09-26 22:08:25 -04:00
ZacharyZhang-NY 99b8a6081b Messaging · Messages: messages in runs, threads, the unread line and receipts
ChatMessage sets a message under its author's picture, name and clock
time. The next by the same author within five minutes, on the same day,
joins the run: only its words, the time in the gutter on hover.
UnreadDivider marks where unread messages start. MessageThread tells who
replied, how many and when the last came; with a handler it opens on a
press, Enter or Space. ThreadPanel holds the first message, a rule
counting the replies, the replies and a composer, its body scrolling.
ReadReceipt says sending, sent, delivered or read, and who saw it in a
group. MessageReactions and TypingIndicator point to collab and motion.

gpui recolors nothing on hover: text takes its color at layout. The
gutter time rests invisible and turns visible through a group hover.
2026-09-26 21:48:17 -04:00
ZacharyZhang-NY cd14b32dfe Messaging · Channels: the chat list, its rows, a channel's head and its pins
ChannelList sets chats down in sections, each a list: a press, an arrow
or Enter opens one, and the open chat is lit. A title names one section,
and a section holds a chat. ChannelItem shows a hash or a lock, its
unread count, a pin when pinned, and goes quiet when muted.
DirectMessageItem shows a person's initials with their presence. Unread
names stand out through ListItem's new strong; muted channels and
offline people go quiet through its quiet. ChannelHeader holds the name
and topic beside members, pins and a call, wrapping by the header rule.
PinnedMessages lists pins newest first; a press, an arrow or Enter opens
one, and Unpin lets one go. With none it shows a line and takes no Tab
stop.
2026-09-26 21:30:17 -04:00
ZacharyZhang-NY 85df47fd5c Files: a press on a download's button stays with the button
gpui runs the click of every pressed element under the pointer, so a
press on a finished download's Show button also opened the file through
the pressable row. The actions' box stops the press on mouse down, so
the row starts no click of its own.
2026-09-26 21:12:38 -04:00
ZacharyZhang-NY 4ef9830def Files · Transfers: file operations, a transfer queue, downloads and storage
A file on its way is a Transfer with a state: waiting, moving at a rate,
paused, done or failed with a reason. One row draws it for the queue and
the downloads: its icon, its way, a bar while it moves or waits paused, a
status with the time left, and only the actions its state allows.
TransferQueue counts what moves and waits. DownloadManager keeps the
finished below under Clear, opening on a press and showing their folder.
FileOperationProgress tells a copy or move with its bar, time left and
current file, its header wrapping by the header rule; a failure speaks in
the danger tone. StorageUsage points to data_display's UsageBar.
2026-09-26 21:05:59 -04:00
ZacharyZhang-NY b280929d07 Files: recent and found files keep the cursor on the file, not its place
RecentFiles and FileSearch keyed their rows by place in the list, so when
an open moved a file to the top, the cursor stayed at the place and the
next Enter opened another file. Rows are keyed by the file's folder and
name, asserted once a list, so the cursor follows the file. A dotfile's
kind reads by its extension, as its icon does.
2026-09-26 20:55:21 -04:00
ZacharyZhang-NY d76a489934 Files · Find and look: preview, recent files, search, duplicates and archives
FilePreview shows a file large: its picture in the host's shape, the first
lines of its text, or its kind's icon, over its name, kind, size and
change; the gallery opens it on Space as a quick look. RecentFiles lists
files newest first under a header a day, a list a day. FileSearch sits
over a field the host owns and lists what the host found with its folder.
DuplicateFinder marks copies to remove and keeps the last of each group,
its mark shut, with the bytes the marks free beside Remove. ArchiveViewer
draws an archive's entries as a tree with sizes and savings, from paths
gathered by the folder builder the file tree now shares. A checkbox's
label wraps inside its box instead of running past it. Favorites and the
hex viewer point to documents and debug.
2026-09-26 20:42:06 -04:00
ZacharyZhang-NY 4717aad596 Files: a narrow listing keeps its names; a column starts fresh for a new folder
The directory listing's size and date columns are fixed, so at 280px the
name had no room and rows showed no names. The heading and rows now sit
in a sideways scroll box whose child keeps the least width: the name's
least, the fixed columns and each row's icon, gaps and padding. A column
of the explorer was keyed by its depth, so a new folder there inherited
the old list's cursor; columns and rows are keyed by their folder. The
explorer asserts each folder on its path is a folder in its parent.
2026-09-26 20:22:12 -04:00
ZacharyZhang-NY 14de69fed6 Files · Explorer: a folder as a list, tiles or columns, with one path and one order
FileExplorer shows the folder at the end of a path three ways: the
directory listing, a grid of tiles, or a column per folder along the
path with the next folder lit. Its name, its count and a switch of views
sit above one path bar that climbs. A folder opens in place; a file goes
to the host, which lists each folder it goes to. A pick in an earlier
column closes the columns past it, and a new deepest column scrolls into
view. The directory listing takes its pick from its owner and reports
it, so the pick carries across views; its path bar and its first order
are shared. The gallery walks a small file system and shows the tree,
the context menu, renaming in place and a drop zone in use.
2026-09-26 20:07:33 -04:00
ZacharyZhang-NY 501d1b0f1a Files: a tile's name keeps its ends; the ellipses share one line cutter
gpui's line_clamp wraps once and lays the rest of a long name on the
second line, which a centered tile then clipped at both ends. A tile's
name is now one line, cut in the middle so the front and the extension
stay. MiddleEllipsis draws like Ellipsis: in the text style around it,
sized by its text, so a short name centers; the two share one cutter.
2026-09-26 19:51:43 -04:00
ZacharyZhang-NY a846e10847 Files · Items: one icon map, file icon, file row and file grid
The crate had two maps from a file's name to its icon, in chat and in the
file tree, and they disagreed. One map now lives in primitives, over the
Lucide file-* icons, and chat, agent, the file tree and the directory
listing use it. FileIcon draws it for a file or a folder. FileItem is a
file's row, a ListItem with its size and when it changed. FileList is the
directory listing. FileGrid lays tiles in the order given, with a cursor
seeded from the owner's pick: arrows by tile and by row, Home, End, Enter
and a double press to open. The count of what fits a width has one home,
layout's columns_for, and fit builds on it.
2026-09-26 19:40:10 -04:00
ZacharyZhang-NY e75171c995 Forms: an option list stays inside the window; media ratios are checked in one place
The floating list under a Select, Combobox or MultiSelect was at least its
trigger's width with no ceiling, so a long choice ran it past the window
and its rows never ellipsized. It now stops at the window's width less the
window margin on each side, so gpui snaps it inside. The camera preview
and the model viewer check their ratio through primitives::checked_ratio.
2026-09-26 19:17:52 -04:00
ZacharyZhang-NY d1b8ad395d Media · Capture and devices: recorder, camera, mic meter, device choice and 3D model viewer
ScreenRecorderControls holds a recording in one pill: Record, then a live
dot and the time it has run beside pause or resume and stop, with mic and
camera toggles. CameraPreview shows host frames in the camera's shape, or
the camera off. MicLevelMeter lights segments up to the level, amber and
red near the top, with tones shared from data_display. DeviceSelector
picks a microphone, camera or speaker through Select, which may now be
empty while shut. ModelViewer shows the host's frame for an orbit that a
drag, the arrows, Command-scroll, + and - and a reset move; the orbit is
seeded from the host so fast drags add up. The gallery draws a stone
torus per orbit and frees each replaced frame.
2026-09-26 19:05:23 -04:00
ZacharyZhang-NY cb6c78b90c Lists: a selectable list's cursor starts on the owner's pick; the waveform's fit is tested
SelectableList keeps the owner's last selection and the one it last
sent. A selection the owner makes, on first paint or later, moves the
cursor to its first row; the echo of the list's own pick leaves the
cursor where the pick put it. The playlist's keys start from the song
under way. The waveform's bar count and inset move into `fit`, tested
beside `resample`.
2026-09-26 18:44:38 -04:00
ZacharyZhang-NY 04f88812dc Media · Audio and controls: player, waveform, spectrum, volume, speed, transport and playlist
AudioPlayer sets a song's cover, title and artist over a scrubber with
the time played and left, the transport and the volume. AudioWaveform
draws peaks gathered to the bars that fit, dark as far as it has played,
and seeks by press, drag and keys, with the time tip the scrubber shares.
AudioSpectrum shows levels by pitch. VolumeControl mutes from its speaker,
and moving its slider while muted unmutes. PlaybackSpeedControl picks from
a menu. MediaControls holds shuffle, back, play, forward and repeat.
Playlist builds on SelectableList: the song under way is lit with moving
bars, and Enter or a double press plays another.

The video player's bar now rests while the stage itself holds focus, a
key it takes wakes the bar, and a press on a resting bar only wakes it.
2026-09-26 18:32:45 -04:00
ZacharyZhang-NY 1ac50e4002 Media: the player's bar fades by elapsed time, so its scrubber and buttons keep their state
The fade restarted an animation id at each rest and wake, and gpui hands
that id to every descendant: the scrubber's drag, hover and focus handle
and the buttons' focus were rebuilt under it. The bar's opacity now reads
the time since the last change through motion::since_change. The stage
tracks focus even without key handlers, so a focused bar button keeps
the bar up. The player's tests move to their own file.
2026-09-26 18:14:06 -04:00
ZacharyZhang-NY f8fcfa2870 Media: the caption outlasts the bar; one Space per press; narrow boxes hold
Only the bar fades now, so the caption stays while the controls rest, and
the caption chip can shrink, so a long line wraps inside a narrow player.
A focused media button keeps Space to itself, since gpui presses it on
the release. The player and the strip let Command and Control letters
pass. The scrubber's tip splits the free width by the pointer's share and
stays over the track at both ends. An inline edit's label gives way with
an ellipsis beside fixed parts.

The player is a Tab stop only when a key does something, logs its
captions, keys its fade by its own id, and logs a failed wake.
2026-09-26 17:52:27 -04:00
ZacharyZhang-NY 99b6ccd798 Reviews: T26b passes 2026-09-26 16:46:40 -04:00
ZacharyZhang-NY 1788753f6d Media: a shaped corner follows the pointer up and down; the fitted crop is told once
With an aspect, a corner drag sizes the crop by whichever side the pointer
moved further, so a drag straight up or down resizes it too. The upload
tells the host the fitted crop once per file, whether the host keeps it or
not; a host that dropped it had set off a loop of reports. The annotator's
Escape stops only when it lets a chosen mark go.
2026-09-26 16:38:22 -04:00
ZacharyZhang-NY 4acedc60c0 Reviews: T26a passes 2026-09-26 15:58:02 -04:00
ZacharyZhang-NY 0ccf4c1587 Media: the viewer's picture moves; a viewer with no picture takes no Tab
The stage's picture carries an id, so gpui keeps its frame and asks for
the next: an animated picture plays, turned or not. A viewer whose picture
has not loaded or cannot open is no Tab stop. AGENTS.md: img() moves an
animated picture's frames only with an id.
2026-09-26 15:51:03 -04:00
ZacharyZhang-NY 9ed801b67d T26a Media: images
A new chapter, src/media. ImageViewer fits a picture to its stage and
zooms by the document steps, by keys, buttons and Cmd-scroll about the
pointer; a drag through gpui's drag, a scroll or the arrows pan it once it
overhangs; R turns it a quarter at a time. gpui paints pictures without a
transform, so media::turn turns the decoded frames off the main thread,
an asset keyed by the picture and the turn; `image` 0.25, which gpui
already decodes with, is now a dependency. ImageThumbnail shows a picture
in its own shape with a corner label and a ring when chosen, and is a Tab
stop only with a handler. Lightbox / Gallery Viewer points to
overlays::Lightbox, which the gallery opens from the thumbnails.

T26 splits into T26a–e. AGENTS.md: the drag rule says drags that pan,
turn or slide go through gpui's drag; pictures turn by their pixels.
2026-09-26 15:34:45 -04:00
ZacharyZhang-NY 14f67f8429 Reviews: T25f passes 2026-09-26 15:22:31 -04:00
ZacharyZhang-NY 3d9cf1008e Generative: the embedding turns by drag past its edge; version diffs are tested
The turned embedding turns through gpui's drag, marked with its view, so a
drag keeps turning past the plot's edge. What a chosen version is set
against, and its heading, move into versions::compared, with a test. A
hidden legend entry dims its key and not its ring, and an embedding refuses
a group named twice.
2026-09-26 15:17:45 -04:00
ZacharyZhang-NY 2f859eeb03 Reviews: T25e passes 2026-09-26 14:02:23 -04:00
ZacharyZhang-NY 7c6b4c883d Description rows wrap; the fine-tune share is tested
A DescriptionList row drops its value under its label once the value has
less than half a label's width beside it, so a 280px model card no longer
breaks its facts mid-word. FineTuneJobCard's bar share and its epoch line
move into tuning::progress, with a test.
2026-09-26 13:57:14 -04:00
ZacharyZhang-NY 5ba76ace99 Reviews: T25d passes
Round 2 of the Claude reviewer found no defects.
2026-09-26 13:37:25 -04:00
ZacharyZhang-NY 396531c075 Generative: the video strip scrolls and reveals; tags wrap in their row
The timeline's scroll box is the flex row itself, so its reach is its
shots; a focused shot brings itself whole into view through
layout::bring_into_view, which table headers now share; Extend sits
with the caption under the strip. A voice's tags cap at the row's
width and wrap inside it.
2026-09-26 13:32:44 -04:00
ZacharyZhang-NY eb0634e7a0 Reviews: T25c passes
Round 2 of the Claude reviewer found no defects.
2026-09-26 13:09:53 -04:00
ZacharyZhang-NY 23e65d75b9 Generative: a mask shown to read takes no pen; strokes fail loud
InpaintCanvas is a Tab stop with the focus ring only when on_brush
serves its keys, and takes the pen, the crosshair and the ring only
with on_stroke. new() asserts each stroke's radius within REACH and
its points on the picture. The settled strokes' cells stay cached, so
a drag paints only the stroke under the pen over a copy. The canvas
keeps square corners, so the wash fits the picture. The gallery drags
with moves.
2026-09-26 13:05:34 -04:00
ZacharyZhang-NY c349a025e5 Reviews: T25b passes
Round 2 of the Claude reviewer found no defects.
2026-09-26 12:47:12 -04:00
ZacharyZhang-NY 03e36ab0ef Generative: a picture with nothing to open takes no focus
A done result tracks its focus only beside its press, so a grid with
no open handler leaves focus where it was. A decided comparison drops
its empty verdict row, so no gap is left under the sides.
2026-09-26 12:44:18 -04:00