feat: content search, unified toolbar, full-window tasks (0.10.0) - #29
Merged
Conversation
The sidebar search only ever filtered filenames, titles and tags. The existing search_docs_core walks the workspace status folders, so it finds nothing in an arbitrary opened folder and could not be reused. Add a content search built on the scan traversal: - Extract collect_markdown_entries from run_scan so the file tree and content search share one definition of which files a folder contains. - Add core::search: rayon-parallel scan per query, matching through the regex crate with escaped terms so input stays literal and case folding does not shift the offsets used to build snippets. - Return pre-segmented snippets rather than offsets. Rust byte offsets and JavaScript UTF-16 indices disagree on any document containing an accent or an emoji, so no index crosses the process boundary. - Share the field weights between the in-app and MCP scorers so the two rank the same corpus identically. - Cancel superseded queries with a generation counter: a Tauri command cannot be aborted once running, so a stale search bails out instead of competing for the disk. Also lands the DOM range engine that will back find-in-document.
Typing a query now searches document contents alongside names, titles and tags, and the sidebar shows a ranked result list with the matched line and its surrounding context. - A non-empty query replaces the lens body with results, so a file that matches by both name and content is listed once rather than in two places. Clearing the query restores the lens. - Name matches render immediately from the in-memory list and are re-ranked in place when the scored content hits land, so the list fills in instead of flashing empty. - Snippets render from pre-split segments as text nodes, so no markup from a document can reach the DOM. - The footer match count and the search placeholder now reflect that contents are searched. Note: typing a query while the tasks lens is open now shows search results rather than being silently ignored.
Cmd+F opens a find bar scoped to the focused pane: all matches are highlighted, Enter and Shift+Enter step through them, the focused match is centred, and the count reads "3 of 17". The rendered document is React-managed DOM, so nothing may mutate it. A rehype pass injecting <mark> would also re-run the whole unified pipeline, including Shiki's WASM tokenizer, on every keystroke. Matches are therefore modelled as DOM Ranges and painted outside the document tree: - On webviews with the CSS Custom Highlight API, ranges are handed to the engine and nothing is created at all. - Older webviews get positioned rects in a container that is a sibling of the document. macOS 11 tops out at Safari 16.6.1 and can never have the Highlight API, so the fallback is required rather than optional. Both paths share one match engine and one stylesheet, so they look the same and only the paint step differs. Live ranges collapse silently when React replaces nodes, so a MutationObserver rebuilds them, ignoring the overlay's own writes to avoid a paint loop.
Paint was keyed on matchCount and currentIndex, but the ranges live in a ref and so cannot drive an effect. Editing a query into one that matched in the same number of places left the previous ranges on screen: typing "board" kept painting the "bo" ranges, because both match five times. Found by running the app; the unit tests missed it because each one moved between queries with different match counts, so the effect always happened to re-run. A revision counter now signals every recompute, and two regression tests cover a query that lengthens and one that shortens without changing the count.
Adds a scope to the core query so a caller can decide which fields count. Defined once in Rust and mirrored once in TypeScript, since the pair is a closed set that would otherwise drift. The names scope matches the whole workspace-relative path, so a folder name is searchable the way a file name is.
Search results now live in their own lens rather than hijacking whichever lens was open, and follow the shape editors have settled on: results grouped into the files that contain the term, each with its match count, expandable to every matched line. - Shift+Cmd+F opens the lens and focuses the query, alongside Cmd+F for the open document. The two chords never contend. - A scope row narrows the search to names, contents or tags, backed by the core scope rather than filtered client-side. - Quick Open stays a separate file-name-only surface across every open workspace, which is the split editors already train people on. Typing a query no longer replaces the tasks board or the file tree.
Instant name matches were merged into the results whatever the scope, so narrowing to contents or tags still listed files that had only matched by name and the filter looked inert. The client-side filter spans names, paths, titles and tags, which is a superset of any single narrower scope, so it is only sound to seed when searching everything.
Quick open is the most-used way into search, so it now lists matches from inside documents beneath the file-name matches, each with the line that matched. Name ranking is untouched and still resolves synchronously, so the list never lags a keystroke. Quick open spans every open workspace while the search command took a single folder. Running one request per folder would have broken cancellation, since the generation counter is shared and each request marks the previous one stale, so folders would have cancelled each other. The command now takes the folders together and every hit reports which one it came from. Also renders the real shortcut glyph in the header rather than the raw setting string.
Adding the Search lens made six tabs, which no longer fit one row at the default sidebar width. The row had no width constraint, so the last tab rendered outside the sidebar and painted over the open document. The row now wraps. Clipping it instead would have hidden a primary entry point behind a scroll, which is worse than a second line.
Quick open now carries the same scope filter as the search lens, so the list can be narrowed to files, contents or tags without leaving the dialog. Narrowing to contents drops the file-name group, narrowing to files drops the document matches. The "names" scope is now labelled Files, which reads better than Names on both surfaces. Only a tag search legitimately returns a hit with no matched line, so elsewhere those stay out of the "in documents" group rather than appearing under a heading that misdescribes them.
Quick open's binding was already a stored setting, but find-in-document and workspace search were hardcoded at their use sites. All three are now settings with defaults, editable together in Settings, and read live so a rebind takes effect without a restart. A cleared or malformed stored binding falls back to its default instead of leaving the action unreachable.
Search was one line and predated the Search tab, the scope filter, and content matches in the file switcher, so a reader could not tell the surfaces apart or find their bindings.
The switcher was a horizontally scrolling row of names: with more than a few workspaces most were hidden behind the scroll arrows and the active one was distinguishable only by font weight. It is now a single trigger showing the active workspace, opening a menu that lists every workspace with add and remove actions. Remove sits in a submenu rather than as a per-row control, since removal is unconfirmed and a trailing button inside a menu item is both mis-clickable and unreachable under the menu's roving-focus model.
Six lenses no longer fit one row at the sidebar width. The row wrapped, which made its height depend on the sidebar width and shifted everything below it whenever the sidebar was resized. The lenses now sit in a narrow rail down the left edge, each an icon with its label beneath it, so the control keeps a fixed height and scales to further lenses without reflowing. The sidebar widens to 20rem to pay for the rail and leave the content column wider than the whole sidebar was. The header's hardcoded left offset duplicated the sidebar width; both now read one declaration.
An agent asked to track work for a project with no workspace could not work out where it belonged: it inferred from an init refusal that a git repository blocks workspace creation, then fell back to the shared user workspace and used a label in place of isolation. None of that inference was correct. Workspace creation never consults git, and staging on write only adds the file just written, inside the workspace folder, never a commit. The onboarding resource now says so, gives an ordered choice of workspace locations including the sibling folder for when a project tree must stay untouched, and states that labels do not separate projects. The init recovery hint no longer reads as though the folder's location is the blocker.
The top bar spanned only the document area while the sidebar carried its own header, so the window showed two unrelated strips of chrome at different heights, and the bar shifted whenever the sidebar toggled. There is now one toolbar across the full window width, holding the workspace switcher beside the window controls. It moved inside the sidebar provider so it can reach the sidebar context and inherit the shared custom properties, which removes the duplicated width declaration. Toolbar height is declared once and the sidebar and document area both offset from it, replacing three copies of the old literal. The window-control inset now depends on the platform rather than applying everywhere, and the platform check lives in one module instead of being redefined for shortcuts. The switcher moves next to the toolbar that owns it and drops its sidebar padding.
…dule The sidebar width and toolbar height are read by the toolbar, the sidebar, and the document area. Keeping them in the app root made every change to either value a change to the root component.
Agents create workspaces without checking what exists, then read the resulting conflict as a failure and go looking for somewhere else to write, which is how work ends up scattered. They also pass names like notes or docs, which is useless the moment a second workspace exists. The guidance now establishes list, reuse, create as the order and says plainly that an already-a-workspace answer means the workspace is ready. The naming rule sits on the creating tool itself, where an agent will meet it without having read the onboarding resource first. The conflict recovery hint no longer reads as an obstacle to route around.
The bar returns to its original height now that the switcher shows the workspace name on one line instead of a two-line identity block, and the macOS window controls are positioned to sit centred in it rather than against its top edge. The switcher gains a surface of its own so it reads as a control, and sizes to its content instead of reserving a fixed width. The badge no longer disappears on hover. It was drawn with sidebar tokens while rendering on a popover, and the menu forces a colour onto every descendant of a hovered row: in the light theme the forced colour and the badge background were the same lightness, so the letter vanished into it. It now uses tokens belonging to the surface it renders on, and a test asserts the contrast survives the forced colour in both themes. Menu rows show a pointer cursor. An unnamed workspace no longer labels itself notes. Managed workspaces all live in a folder of that name, so every one of them rendered identically; the label now falls back to the project above it. The space reserved for the window controls is computed from where they are configured to sit, and a test holds the two files in agreement.
The sidebar carried a Search lens, an always-visible search field, and a separate free-text filter on the tasks board: three entry points for one intent, none of which worked on the others' content. There is now one search. A magnifier in the sidebar header reveals the input and its scope filters, dismissing returns the space, and the query applies to whichever lens is showing, including tasks. The scope filters become chips and are hidden on the tasks lens, where document scopes have no meaning. The board keeps its priority and label filters and loses only the redundant text field. Search is no longer a place you navigate to, so it leaves the lens rail. A stored selection naming it already degrades to a valid lens.
Two workspaces could carry the same slug, and every call naming it went to whichever registered first. No error, no warning: work landed in the wrong folder and reported success. A slug the caller chose is now refused when another workspace already holds it, naming the path it collides with. An agent keeps the slug it passed and will pass it again, so silently suffixing would hand back a name that resolves somewhere else; a refusal is the outcome it cannot miss. A slug derived from the folder name is suffixed instead, through the same helper the conversion path already uses, and the assigned slug comes back in the result. Resolution no longer takes the first match. It collects every candidate that still exists and reports the ambiguity when more than one remains, which also closes the case of a registered workspace and a different unregistered one sharing a slug. An existing registry is left alone: it loads unchanged, non-duplicated slugs resolve as before, and only the duplicated slug now fails loudly.
The registry recorded a slug at creation and nothing ever re-read it, so a folder whose marker was edited kept resolving under the old value and a folder that had been deleted was still advertised as available. The duplicate-slug message had to ask the user to repair two files by hand, because nothing reconciled them. Listing and resolution now take each slug from the folder's own marker and drop entries whose folder is gone. This is read-only: the stored registry is never rewritten, so an existing one keeps working and no migration runs behind the user's back. A folder reached by two spellings counts once, compared by identity rather than by text.
Collapsing hid the whole panel, rail included, so every lens became unreachable and the only way back was a control in the window chrome. The panel now collapses to the rail: the content column goes, the lenses stay, and choosing one brings the column back. The toggle moves out of the toolbar into the rail, where it keeps one position in both states. Giving it a home only while collapsed made every lens item shift down the rail as it appeared. Its icon points the direction it will move the panel. Rail items share one rule for their width, padding, and centering, so the toggle and the lenses cannot drift apart, and the rail takes its width from the shared token rather than a literal. Also fixes a tab that could sit on Loading forever: a read settling before React committed the state that started it found a stale reference, discarded its own result, and had already cleared the timeout that would have rescued it.
Find-in-document does not match rendered math, and nothing said so. KaTeX marks its own visible layer aria-hidden, so the existing rule excluding hidden content already skipped both halves of every formula. Keeping it that way is deliberate: the visible layer's text is ordered by CSS rather than by document order, so a fraction serialises with its denominator first and any highlight would land on the wrong glyphs. Math stays findable by its LaTeX source, which workspace search reads. A test built from real rendered output locks the assumption, so an upstream change that stops hiding the visible layer fails loudly. Also corrects the sidebar search description, which still described the tab it had before it became a revealed panel.
The app declared macOS 10.15, which it cannot run on: the interface needs Safari 16.4, and Catalina's last Safari is 15.6.1. Safari 16.4 shipped for Big Sur, so 11.0 is the lowest version that can reach it. Big Sur and Monterey do not ship that Safari, they only accept it through a software update, so the requirement is stated in the README rather than implied by the number alone. macOS 13.3 is the lowest that qualifies with no user action; choosing it instead would be a one-line change and would drop an order of magnitude more machines. The published cask gains a matching requirement, in the workflow that generates it rather than in the tap, which is overwritten each release.
The reconciliation doc block restated the three steps its body already reads plainly, and the two helpers below it already carry the WHYs that are not obvious. Only the part a reader could not infer survives: that reconciliation never touches the stored file. Also corrects a comment written with a triple slash, which in TypeScript is reserved for compiler directives.
The bar renders inside the element it searches, and nothing excluded it, so its own text was walked in. Counts included the bar and a highlight could paint on it. Worse, a query that appears in the bar's own label never settled. Searching "no" against a document with no matches showed "No results", which the walker then found, so the label became "1 of 1", which contains no match, so it returned to "No results" and round again. The character-data mutation that drives each cycle targets a text node, and the guard meant to ignore the find bar's own writes only matched elements, so it never caught it. The bar is now marked as chrome, excluded from the walk, and its own mutations are recognised whether they land on an element or on text. Range building is capped so a single-character query cannot allocate one per match across a long document.
The installer branched only on architecture, so it would place the app on a Mac whose Safari is too old to draw the interface, after a sudo prompt and with no explanation of what went wrong. It now reads the system version and stops below Big Sur, and warns below 13.3, which is the first release shipping the required Safari. The lower bound tests 10.x below 10.16 rather than a major below 11, because a Mac reporting 10.16 is Big Sur under the old numbering and must not be turned away. Release notes now lead with the requirement, since a user already on an unsupported Mac cannot be reached any other way: the updater has no concept of a minimum system version, so nothing in a published build can stop it offering an update that will not launch.
…failed One abort counter was shared by every search surface, so typing in the file switcher cancelled the sidebar's in-flight query. The sidebar then discarded the result, left its spinner running, and kept showing stale matches with no sign anything had gone wrong. Each surface now has its own counter, keyed by an identity the hook generates, so the two stop cancelling each other; a surface superseding its own request also clears its progress rather than waiting for a result that is not coming. A root whose search failed was skipped in silence and the command still reported success, so a workspace whose folder had been deleted read as 'nothing matched'. Failed roots are now carried back, and the error the results view already knew how to render is raised when every root failed. Partial failures keep their matches: hiding real results behind an error costs the reader more than the missing folder does. Cancellation is now checked while walking the tree and between roots, not only per file, so an abandoned query stops instead of paying for the traversal that dominates a large workspace. The number of hits is capped and reported through the existing truncated flag. Creating the default workspace on first write now hands back the slug it was actually assigned, so a caller cannot build an identifier naming a workspace its file did not go into.
A write with no workspace argument, from a folder with no workspace above it, resolved to the user default and created it on the spot. An agent doing project work had that work absorbed into a shared workspace named after nothing, with no sign anything had gone wrong. Such a write now offers the workspaces that exist, and refuses with that list when the caller cannot be asked. An explicit workspace argument is still honoured even when it names the user default: that is a choice, not a drift. Reads are untouched, so a clean session can still look around. This retires create-on-first-write, which existed so agents never met a setup wall. The wall is the point: init_workspace is now the only thing that creates a workspace, and the refusal names the alternatives.
The lens could only be a board, and the switch would have been another comparison against the lens name, which the sidebar already does in four places. A lens now declares which views it has, and the header renders the switch from that declaration rather than from a name. Only tasks declares more than one today; a second lens gaining views needs no change to the shape that stores the choice. The board kept everything that was never board-specific: the error line, the skeleton, the empty state, and the label and count it publishes to the header. Those moved up to the lens, so both views share them and the list was a new file plus an entry rather than an edit.
The stylesheet was the only thing told which scheme was showing, but the close and minimise controls are drawn by the OS from the window's own appearance. Choosing dark while the system stayed light left them drawn for a light window over a dark toolbar, which is why they only appeared on hover. The choice now reaches the window as well. Following the system passes no choice at all, rather than resolving it and asserting the answer, so the OS keeps deciding.
The accent rework, the tasks list view, the single refresh control and the header count all landed undocumented. The MCP page still promised that an omitted workspace falls back for writes as well as reads, which the server no longer does. Corrects one claim in passing: reads still resolve to the user workspace, so a session with nothing set up can look around. Only writes refuse.
The default variant carried the badge's link-only hover idiom, which compiles to a rule matching anchors alone. No hover rule reached a real button, so every primary button in the app was inert under the pointer. Reinstalled from the registry rather than hand-edited. Git history shows the variant was never a deliberate local change.
Measured in a running window for the first time: the controls sat 2px high, and the gap beside them was 6.5px rather than the 12px intended. Two wrong numbers. The vertical value is not a top offset: the platform centres the controls inside a band of button height plus that value, so it has to be the bar height minus the button height. And the group spans 60px, not the 54 assumed when nobody could measure it.
The first pass asked whether the shared workspace carries a marker, which is a question about the folder rather than about the caller. Once it has been set up, and it always has been after first use, every unplaced write resolved to it as though it had been chosen, so the original complaint was never actually fixed. The resolver now asks whether anything covering the caller's location names a workspace. The shared one counts only when the caller is standing inside it, which the walk-up cannot report for itself because it looks one level down. Standing in the home directory does not count. A shell starts there, so it says nothing about intent, and admitting it would leave the hole open for any agent launched from home. Also fixes the walk-up comparing paths literally, which missed when the working directory and the home variable spelled the same folder differently and let the shared workspace be reported as a project.
The page still described the refusal as firing only when the shared workspace has never been set up. It fires wherever the agent is working outside a workspace, which is the case that matters.
… bright Three things the first look at a running window exposed. Slate was full chroma on a blue hue, so it rendered as a second blue and the picker showed what looked like a duplicate entry. It is now a cool near-neutral, shaped like the bronze already in the table. The accent chips carried a faint rim, so the black one read as an empty slot on the dark card. The rim now carries the edge, for every chip rather than that one. The editor drew its caret from the muted foreground, which is right for icons and placeholder text and wrong for the one mark that must stand out more than the text around it. Carets now have their own colour. Inline code went the same way: the editor coloured it from its own palette while the reader used the muted chip, so one document had two looks depending on the mode.
Nothing anchored the page height, so the shell sized itself off the viewport with a minimum rather than a height. A minimum gives the panes nothing definite to divide, so a long document grew the page instead of scrolling inside its own pane: a second scrollbar appeared beside the document's own, and scrolling it carried the tab bar off the top. The height now starts at the root and every panel fills its parent.
A draft was seen disappearing once when the theme was toggled with the editor open, and could not be reproduced. Tracing the path shows it cannot: applying a theme touches only DOM React does not own, and nothing on the editor's mounting path derives from anything it changes. The test drives every scheme and accent change against a live editor holding unsaved text and asserts the same instance survives. It was checked against a deliberately keyed editor, which it catches, so it would fail if the mechanism were ever introduced.
The refusal had tests for the cases it blocks but none for the two it must let through: a write from inside a project workspace, and reads from anywhere. Both are the ways the policy could overreach, and both were only covered by the fix's own reasoning. Each was checked by breaking the behaviour it pins.
The board lived only in the sidebar, about 260px wide, so three status columns would have been 80px each and it stayed a stacked list. Giving it width meant the main area had to hold something that is not a file, which it had never done: a tab was a path read from disk, and the tabs it remembered were a list of paths. A tab now points at a kind and a reference, and the kind says whether anything is read from disk at all. Loading, watching, editing, checkbox writes and scroll memory all follow from that one answer rather than from asking what a tab is. The pane looks its content up by kind, so it no longer knows what a file is. Remembered tabs carry the new shape and read the old one, so an upgrade keeps its tabs. Note the write is one way: a store rewritten by this version reads as empty to an older build. The side-by-side board is only reachable in the main area, where the width exists. The sidebar keeps its own two views and its own switch.
Found by watching the app run rather than by reading it. The entry point kept a listener on the system scheme for the life of the window, so a reader who had chosen light watched the app turn dark when the system did at sunset. Nothing corrected it: the hook that owns the scheme only reacts to the setting changing, not to the class being flipped underneath it. The first guess stays, since it is what stops the window flashing the wrong background before the stored setting loads. The tab strip asked for sideways scrolling only, but setting one axis makes the browser compute the other to auto as well, so a stray pixel of height painted a vertical scrollbar over the tab titles. In a split it sat on top of the active tab's name. The window controls were still off centre, now by two the other way. The top edge tracks the configured value minus nine, measured at two settings; both earlier values came from reasoning about what the platform ought to do, and only measuring settled it.
Ten images of an interface that no longer exists: horizontal lens tabs, the always-on sidebar search box, the file-count footer, refresh in the window toolbar. No page links to any of them, so their only remaining use was to be picked up by mistake. Git history keeps them for anyone who wants to see what changed.
Two effects raced over one flag. The layout effect set it to say the document had been placed; a plain effect cleared it whenever the tab changed, and on mount that one always ran second. A pane that mounted with its document already in hand therefore ended up marked as never placed, which silently disabled the guard on scroll recording, so the position was never remembered again. Worse, the restore stayed armed: the next rewrite of the file, which agents do constantly here, dropped the reader back at a stale offset. Toggling the split does exactly that, rebuilding the pane around content it already has. The flag is now the path already placed, compared where it is used, so there is nothing to clear and no second effect to order against it.
The board is already a view of the whole workspace, so the explorer next to it repeated itself in exactly the width the columns needed. Each tab kind now says whether it wants the explorer beside it, so the rule is the kind's own answer rather than a test against its name. The remembered preference is left alone while such a tab is showing, so an ordinary document still opens the way the reader left it, and asking for the explorer there is answered separately: the toggle keeps working, which matters when the board is the only tab open.
Highlights from a shorter query were seen surviving into a longer one that extends it. The painting path turns out to clear before every paint, on both painters, and the symptom would not reproduce at the painter, the hook, or in either browser engine. So these tests pin the clearing rather than fix anything. Each asserts first that the painter it means to exercise actually ran, since the two are chosen from what the environment offers and a test that silently exercised the other one would prove nothing.
Every shot predated the sidebar rework. They now show the lens rail, the single toolbar, the header row and the task board, taken from the fictional workspace the set has always used rather than from real work. The hero shot is back on the document that carries all three things its caption promises: a diagram, a checklist and highlighted code. Two are dropped rather than retaken: one duplicated the split-view shot, and the other needed a task left permanently advanced, which would have put the rest of the set out of step with it.
The previous capture predated the change that hides the explorer next to a full-page board, so it showed the sidebar task list and the board side by side: exactly the duplication that change removed.
The window intercepted drag and drop before the page saw it. Its handler reports every drag as handled, and on macOS that path returns an accepted operation without deferring to the engine, so a card's drag started and ended but no column was ever told a drag was over it or dropped on it. Measured in the engine the app ships on: dragstart and dragend fired, dragover and drop never arrived. The mechanism is visible in the webview layer's dragging-destination overrides, which return early rather than calling through. The option that turns this off is documented as being needed on Windows. It is needed here too, and nothing in the app listens for a dropped file, so the interception was buying nothing.
Three surfaces gave three answers: the sidebar could be a list or a grouped board and carried the switch, while the full-page tab could only be columns and could not be switched away from. A view is now a property of the full-page tab alone. It offers a list or a board, both filling the content area, chosen from the toolbar and only while such a tab is showing. The sidebar draws tasks its one compact way and its header holds no switch, so the map of which-lens-has-which-views goes with it: one stored value, one place it is chosen. The columns were named after an industry term nobody outside this trade uses. They are the board now, in the code as well as on screen, and the sidebar's grouped rendering stops being a named view at all. The columns also shared no width, so three of them left a fourth column of empty space. They divide the pane and fall back to scrolling sideways only when they genuinely cannot fit.
Every task was a flat two-line card with its details wrapped underneath the title, so nothing separated one status from another and there was no column to run an eye down. Tasks now gather under a status heading that folds, and each task is one row whose cells line up with a quiet header naming them. Alignment comes from a single column template both the header and the rows use, so a column cannot drift between them, and a task missing a detail leaves that cell empty rather than shifting the row. Folding reuses the memory the sidebar already keeps per workspace, so a status folded in one place is folded in the other. Only fields a task actually carries get a column. The designs this follows show a due date and a comment count; neither exists here, and neither was invented to fill the space.
Releases have carried install instructions and nothing else, so what changed between them lived only in commit history. The entries are written from what the code does rather than from commit subjects, and the branch's own churn is left out: a fix to something introduced on the same branch nets to nothing a reader ever saw. The refusal to write into a workspace nobody chose is called out three times over, since an agent relying on the old behaviour will start getting errors.
The switch moved to the toolbar and the tasks open full-window, but both pages still sent readers to a control in the sidebar header that is no longer there, and called the list flat when it now groups by status. Adds a link to the changelog, and leaves the version in the download links alone: the release workflow rewrites those.
The search sat between two flex-1 spacers, so its position was the midpoint of whatever flanked it. Measured in a running window, it slid 143px between a document tab and a tasks tab: 118px from the breadcrumb shortening and 24px from the tasks view switch widening the control cluster. Three grid tracks with equal flex factors are given equal size, so the middle track holds still whatever the sides do. Measured at 0.0px of movement across both states. It rests at the centre of the padding box, 37.5px right of the window centre because the window-control inset pads only the left; that offset is constant, which is the property that was missing.
Captures the single toolbar, the lens rail, the full-window task board with the explorer stepped aside, and the corrected window controls.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
The 0.10.0 feature line: workspace content search, a single window-wide toolbar with a lens rail, full-window tasks (board or grouped list), and a change to how agent writes resolve a workspace. Full detail in CHANGELOG.md; the highlights below.
Breaking change
A write with no
workspaceargument is now refused unless a workspace covers where the agent is working, instead of silently falling back to the shared personal workspace at~/notesand creating it on demand. Reads still fall back. Onlyinit_workspacecreates a workspace now. Passingworkspaceexplicitly always works. This affects every write tool; see the changelog's Breaking changes section for the full list and the migration note.macOS 11 Big Sur is now the declared minimum (with Safari 16.4+), correcting an earlier claim of 10.15 Catalina that never actually worked.
Added
Changed
Fixed
Window controls drawn for the wrong appearance; a theme choice overridden at sunset; the whole window scrolling behind the app; task drag-to-advance on macOS; missing dialog overlays; scroll position lost in a split-opened pane; a tab stuck on Loading; a stray scrollbar over the tab titles; duplicate workspace slugs; and more - see the changelog's Fixed section.
Verification
469 frontend tests, the Rust suites,
tsc, clippy, fmt and the production build all pass. The toolbar-search fix and the task interactions were verified in the running app, not only in jsdom.