[pull] main from danny-avila:main - #581
Merged
Merged
Conversation
…14990) * feat: leave a dismissable strip of chat beside the mobile drawer The drawer took the whole viewport, so opening it read as a screen change rather than a layer over the conversation, and the only ways back were the header button and a swipe. It now stops at 80% and the chat stays visible behind a scrim, which is itself the dismiss target: tapping it closes the drawer and returns to the conversation, which never navigated away. The scrim renders as a sibling of the pane rather than inside it, because the pane is inert while the drawer is open and would swallow the click. Drawer width and pane travel derive from one constant so they cannot drift. Closing had to change with it. A programmatic close repositioned the pane instantly, which was invisible only because a full-width opaque drawer covered the jump; with a strip on screen that jump lands in plain view, so both surfaces animate together, the motion the drag path already produced. The spec that pinned the old reveal is rewritten to pin this. The easing also changed: the previous curve spent its last third of the duration on a few percent of the distance, which read as the panel sticking just before it landed, most obvious on close. Three things the scrim has to respect, each found in review: - It routes through useSidebarToggle rather than writing the atom, so the slide still starts imperatively and a large conversation cannot stall it. - It drops its fade under prefers-reduced-motion, matching the snap kickDrawerAnimation already performs. - It stays the pointer target until the close animation settles, derived from the committed state so every close path is covered, and cleared on a timer so a scrim unmounting at the breakpoint cannot strand it. Focus returns to the drawer's opener once the closed state commits, since the scrim goes aria-hidden and untabbable. * fix: close the mobile drawer predictably from every path Move the close handling out of Root into useDrawerDismiss, which fixes three things the split scrim-owned version got wrong: A breakpoint crossing derives the drawer closed with nothing to animate, so narrowing a window or rotating a tablet armed the pointer guard and left a transparent full-screen scrim swallowing taps for 300ms. The scrim stays the pointer target through that guard, where the state has already committed. A tap there closed again, a no-op that never reached the focus handoff, stranding the restore flag to fire on a later close. Focus was only restored when the scrim itself closed the drawer, and only to the header opener. Closing from the drawer button or Escape left focus in a subtree that goes inert, and routes that render no opener left it on the scrim once it went aria-hidden. Every close path now restores, to the opener or the pane, and only when the close is what dropped focus. * style: sort imports in the new drawer hook * fix: reclaim focus from the scrim when Escape closes the drawer The drawer's Escape handler closes it without going through the scrim, so a keyboard user who tabbed there kept focus on a button that becomes aria-hidden and untabbable. Inert drops focus to the body by itself; aria-hidden does not, so it has to count as lost too. * feat: make the mobile chat strip a setting, off by default The drawer covering the full width and closing by swipe stays the default. Turning the setting on stops it short of the edge, leaving a strip of the conversation visible that also closes the drawer when tapped. Both surfaces read one custom property for how far the drawer opens, so the value can change at runtime without threading a number through the swipe gesture, and their travel still cannot drift apart. The fallback is the default, so anything rendered outside the property's scope agrees too. The scrim moves into its own component, which is what makes its tab order, aria-hidden and pointer-events states testable. * fix: keep the reveal close on the default full-width drawer Making the strip opt-in put the paired close animation on the default path, where the drawer covers the pane: selecting a conversation then visibly shifted the chat leftward while the new one committed into the moving layer, which is the regression the reveal existed to avoid. The reveal is now chosen from geometry rather than the setting, since it is safe exactly when the drawer hides the pane, however the width was arrived at. The drawer also transitions its width, so toggling the setting while it is open moves both surfaces on one curve instead of jumping the width in a frame while the pane eases across the transition. * fix: honour reduced motion when the strip setting changes the width Changing the setting updates the width custom property directly rather than going through the snap path, so the drawer eased its width and the pane its transform for the full transition even for a user who asked for no motion. The preference now reaches the declarative styles on both surfaces, and the snap no longer hands an animating transition back afterwards, which is what left the element ready to ease the next change. * fix: cover the gesture snap, the close frame and the breakpoint focus The gesture settle restored the transitions directly rather than through the reduced-motion handoff, so a swipe left both surfaces ready to animate the next width change. The close guard was armed from a passive effect, which runs after paint, leaving one frame where the pane had dropped inert and the scrim had not yet taken the pointer back. It is armed in the committing frame now. Crossing into mobile with focus inside the expanded desktop sidebar drops it when that subtree unmounts. The guard is still right to stay disarmed there, since nothing animates, but the focus handoff has to run, so the two no longer share an early return. * fix: keep the pointer guard tied to a pane that actually moves Disabling the strip unmounts the scrim at once while the drawer needs the whole transition to widen, so a close begun in that window still slid the pane with nothing holding the pointer. The scrim now stays mounted while a close is in flight. Arming that guard is tied to the same geometry the close path already branches on. A close under a drawer that covers the pane is a reveal, with the pane already in place, so holding the pointer there would only make the default configuration feel unresponsive for the length of the transition. * fix: guard the swipe close and hand focus back off the mobile breakpoint The guard read the drawer's width to decide whether the pane was moving, but a swipe animates the pane at any width, so the default configuration went unguarded through the one close path that does move it. It now asks the pane itself: the reveal leaves transition none behind, every animated path leaves the shared transition on it before the state commits. Leaving mobile unmounts the drawer and the scrim, so focus sitting on either went to the document. The same handoff runs for that direction, and it now confirms the opener actually took focus rather than assuming: the opener stays mounted across breakpoints but is hidden on desktop. The scrim is imported through the mobile directory's barrel. * fix: address PR review bot findings Codex: - Start the scrim fade with the drawer slide, not the deferred Recoil commit - Keep the scrim focus ring inside the overflow-hidden shell * fix: address PR review bot findings Codex: - Capture pointer events on the scrim as soon as an open slide starts - Expire the close guard at the animation deadline, not a fresh 300ms - Keep pointer capture through a reveal close while the drawer still slides * fix: hand focus back once the close guard releases Codex: - Defer focus restoration until the pane is no longer inert The guard reapplies inert to the pane in the same commit the close lands, and both the opener and the pane itself sit inside it, so the handoff was ejected to the body with no dependency left to re-run it. The release now flushes before focus moves. * fix: drop the scrim pointer override when the close slide starts Codex: - Clear the opening pointer override on close The opening kick writes an inline pointer-events override that only the buffered release cleared, so a dismiss inside that window left the invisible scrim swallowing taps past the guard. The close now hands capture back to the classes, which already hold it for the guard's duration. * fix: carry the focus handoff and the slide's own clocks through a close Codex: - Preserve focus when the motion preference changes mid-close - Keep the scrim armed when an opening is canceled - Stabilize the drawer width before closing mid-toggle The handoff is now keyed off the guard releasing rather than the timer, so a guard cancelled by a dependency change still hands focus back once the pane sheds inert. A close that cancels an uncommitted open never reaches the isClosing classes, so it keeps the scrim's pointer override instead of returning capture that nothing else holds. And the close pins the drawer's measured width, so a width transition still in flight cannot drive its edge from a second clock and open a gap against the pane. * fix: guard a close the committed state never reports Codex: - Guard canceled opens when the strip is disabled - Stabilize the drawer width before an in-flight swipe A second toggle inside the deferred flip, or an open drag that falls short, moves the pane without expanded ever changing, so the guard had no transition to arm from and the default configuration left the pane live as it uncovered. The slide now reports itself and arms the guard directly, which also makes the scrim's pointer override unconditional again: every close hands capture back to the classes. Claiming a gesture drops the transition, which lands a width still easing toward the strip target on that target in the same frame, so the touchstart snapshot went stale and held a gap open between the surfaces for the rest of the drag. The claim remeasures. * fix: cover the opening travel and compose the scrim's button Codex: - Guard the pane during default drawer opens - Compose the shared button primitive for the scrim Recoil's flip is deferred past the opening frames and the closing transition outlives it at the other end, so the committed state brackets the travel too late and drops it too early. The guard is now named for what it measures and arms for any slide the committed state does not report, so the default configuration covers the pane while the drawer travels over it. Only a close records the focus handoff; an open hands focus to the drawer's header. The scrim now composes the shared Button, keeping only the inset ring the overflow-hidden shell requires.
* feat: custom cron cadence for scheduled chats Scheduled chats could only be built from four fixed presets, each pinned to a single hour and minute, so anything outside that shape (twice a day, every 15 minutes, the 1st of the month) was not expressible. This adds a Custom cadence that takes a raw five-field cron expression. The cadence schema becomes a discriminated union on `frequency`. A cron row carries `expression` instead of the hour and minute it cannot represent, since there is no single hour for `0 9,17 * * 1-5`, and the Mongo schema requires each field only for the shape that has it: a blanket `required` would reject every cron write, and dropping it entirely would let a structured cadence silently fire at 00:00 with a missing hour. Five fields only. croner also reads a six-field form carrying seconds and a seven-field form that pins a year, and both are refused. Seconds would promise a precision the engine does not keep, since it polls on a thirty-second tick and offsets each schedule by up to two minutes of jitter. A pinned year makes a cadence that runs out, and every place that computes a next run reads "no next occurrence" as a cadence it cannot read. Compilation, validation, next-run previews and interval measurement live in packages/data-provider so the dialog and the engine share one parser and cannot drift. The dialog previews the next occurrences, enforces the admin interval floor and disables its own submit from the same functions the server validates with, so it cannot offer a Create the API answers 400 to. The interval floor now covers cron, and measures it twice, taking the smaller. The nominal gap is probed in UTC and discounted by the same worst-case DST allowance the structured branches carry, which keeps `0 9 * * *` reporting exactly what the Daily preset reports. Real elapsed time is then measured in the schedule's own zone across each of that zone's transitions, because spring-forward compresses a gap that straddles one: `0 0,12 * * *` in America/New_York is 11 hours that day, not 12, and a floor between the two would otherwise be bypassed. The floor ships with the schedules list so the dialog can mirror it rather than surfacing it as a 400 after submit. Radio gains a wrap variant, since five frequency segments no longer fit one row in a phone-width dialog and a translated label can push even a desktop one over. Its indicator follows the selection across rows; the single-row default is unchanged. * fix: mark the cron input invalid when the interval floor rejects it A floor-violating expression disabled Create and rendered the cadence message, but the input itself still said aria-invalid=false and its aria-describedby never reached that message, leaving a screen reader user with a disabled Create and no stated reason.
…(Stage 2) (#15136) * 🎭 ci: Gate Playwright Lanes and Docker Smokes on Codegraph Selection (Stage 2) * ci: surface the fail-open reason in the stage-2 select summaries * ci: log the stage-2 select decision for harvesting * ci: fail open on fetch failure or truncated file list; type-strict skip decisions (Codex) * ci: check curl's exit status before honoring a selection (Codex r2)
* feat: choose a schedule's timezone A schedule's timezone was whatever zone the browser reported when it was created, and nothing in the dialog could change it afterwards. That is wrong for anyone who travels, for a shared account, and for a team schedule that should follow an office rather than whoever happened to open the dialog. The zone becomes a picker over every IANA zone the runtime knows, with the user's own zone and UTC pinned first. `Intl.supportedValuesOf` is unavailable on older engines, so that pinned pair doubles as the fallback list: a user who cannot browse zones can still keep the one their schedule already uses. Each option carries its current offset, since a name alone does not tell two similar zones apart. A zone change on its own is a timing edit, so it is submitted like one. The server recomputes the next run whenever the timezone changes and measures the interval floor against the effective pair, which is what makes `0 0,12 * * *` a 12-hour gap in UTC and an 11-hour one in America/New_York on the day it springs forward. The dialog now mirrors that: the cron field validates against the selected zone and the floor is measured in it, so a cadence cannot be accepted here and refused by the API. * fix: keep zone-only edits out of the cadence and the zone list findable A timezone-only edit rode the cadence dirty flag, so the PATCH carried a cadence rebuilt from the form and could overwrite stored fields the pickers cannot represent, an API-created hourly's nonzero hour for one. The floor still validates a zone change as the timing edit it is, but only touched cadence controls put a cadence on the wire, and the spec now pins that down instead of only checking the zone. The picker list also gains the modern IANA names supportedValuesOf omits (it reports CLDR's legacy canonical forms, Asia/Calcutta for Asia/Kolkata), each probed against the engine before inclusion, and the per-zone offset labels are cached per locale so reopening the dialog stops rebuilding ~400 Intl.DateTimeFormat instances. * fix: carry the full tzdb rename set into the zone picker Five names covered the famous renames but Node still accepts and omits fourteen more modern canonical identifiers (the Argentina provinces, Indiana and Kentucky city moves, Kathmandu, Asmara, Faroe, Chuuk, Pohnpei, Kanton, Atikokan). The set is now the tzdb rename list, still probed per engine and deduped; deprecated links like US/Eastern and the sign-inverted Etc/GMT forms stay out deliberately, since they duplicate zones already listed under their canonical names.
…Turns (#15138) * ⚡ perf: Stop Awaiting the Conversation Access Marker Write Without Redis the CONVO_ACCESS violations namespace is backed by keyv-file, whose debounced write resolves after ~100ms. validateConvoAccess awaited that write before calling next(), so the first message to any existing conversation waited ~100ms before the request was even admitted — once per conversation per ten-minute window, on every default deployment. The marker only short-circuits the next check, so the write no longer gates the request. The same read now stashes the full document on req.resolvedConversation (null when absent) for downstream consumers. First-turn ack on an existing conversation: 109ms -> 5ms. * ⚡ perf: Read the Conversation Once per Chat Turn A chat turn read the same conversation document four times: the access check (two fields), the subagent thread guard (full document), agent initialization (the files field), and the first save. The access check now reads the full document and leaves it on req.resolvedConversation, the guard accepts that pre-resolved document instead of re-reading, and initializeAgent takes the conversation's file refs from it rather than issuing a separate findOne. Two serial round trips removed from every turn; the same document still serves the first save as before. * ⚡ perf: Remove Duplicate JWT Authentication on Agents Routes routes/agents/index.js applies requireJwtAuth and then mounts the v1 router at '/', which applied requireJwtAuth again. Every request through the agents router — chat turns included — ran the passport strategy twice: two signature checks and two user document reads. The v1 router is mounted nowhere else; its separately exported avatar router carries its own auth in files/index.js. * ⚡ perf: Skip the History Read for Root-Parent Turns and Walk the Tree in O(n) loadHistory fetched every message in the conversation and then walked the parent chain from the request's head. For a new conversation — or a new branch from the root of an existing one — the head is the root sentinel, which no message carries as its id, so the walk was empty by construction and the fetch was wasted. It now returns early. getMessagesForConversation found each ancestor with Array.find inside the walk, O(n^2) on a linear conversation (~5ms at 1000 messages). A Map by messageId makes it O(n); first-match semantics are preserved.
* fix: align subagent activity with chat UI * fix: preserve subagent activity boundaries * fix: preserve subagent panel state semantics * fix: preserve subagent activity metadata * fix: preserve live subagent event metadata * fix: scope subagent phases by message step * fix: retire closed subagent message phases
* feat: weekly schedules on several days A weekly cadence has always stored `daysOfWeek` as a list, and the API has always accepted several, but the dialog offered a single day-of-week dropdown. That picker could only hold `daysOfWeek[0]`, so a multi-day schedule created through the API read back as running on one day, and the form needed a preservation rule to avoid collapsing the rest of the set on an unrelated edit. The dropdown becomes a row of toggles, one per day, so the control can represent what the cadence already stores. The preservation rule goes with it: there is nothing left to preserve once an untouched picker shows the real set. Each pill is a toggle button rather than a checkbox because it renders as one, and carries the long weekday name as its accessible label since "Mon" reads fine at a glance but poorly aloud. The submitted set is sorted, so two schedules picked in a different order are the same cadence. Weekly with nothing selected is expressible in the form but not on the wire, so it blocks submit with a message rather than silently saving as Monday. * fix: keep the weekday pills one line tall and honest about empty sets Seven 3rem pills wrapped to a second line inside their md cell, spending height the dialog's no-scroll budget does not have; they now share the row equally with the locale's narrow weekday labels, composed on the shared Button so the pills carry its focus ring rather than a bare feature-styled element. Both labels are built once per locale instead of fourteen Intl constructions per keystroke, and the cadence summary no longer describes the Monday fallback while the form says to pick a day. * fix: name each weekday pill outright on hover The narrow labels repeat within a week and read by position; until the week-order preference lands, the position is fixed Sunday-first, so the title gives a sighted user the full day name without relying on it.
…on Array (#15141) * ⚡ perf: Append Saved Message Ids Instead of Rebuilding the Conversation Array Every saveConvo read every message id in the conversation (sorted) and wrote the array back onto the document — twice per chat turn, O(n) in conversation length, from a write path. The turn's savers know exactly which message they just wrote, so they now pass it as metadata.appendMessageIds and saveConvo $addToSet-s it, skipping the read and the full-array rewrite. Every save without the option — titles, archive, fork, import, threads — still rebuilds from the database, which remains the heal point for the drift that message deletion has always left behind (deletes never ran saveConvo). The array's consumers read presence or length, or use it as an optimistic cache placeholder, so incremental maintenance is behaviorally identical; on traced turns the array stays exactly equal to the messages collection. Per-turn queries: 15 -> 13 (two Message.find gone), and the growing array payload no longer crosses the wire twice per turn. * 🎯 fix: Brand the Lineage-Only Resolved Conversation Instead of Guessing by Shape The resolved-conversation files fast path treated an absent files property as unresolved so the lineage-only partial from a bound agent-event continuation could not silently hide a conversation's uploads. But MongoDB never stores an empty files array, so nearly every real conversation also lacks the property and the fast path never fired — a follow-up turn on an upload-free conversation still paid the getConvoFiles round trip. The synthesized partial is the one object that cannot speak for the database, so it now carries an explicit symbol brand (PARTIAL_RESOLVED_CONVERSATION, non-serializing and invisible to key iteration), and a stored document without files means what it means: no files. Traced follow-up turns drop from 14 queries to 13. * 🧪 test: Expect the Appended Message Id in the Route's saveConvo Metadata messages-get.spec.js pins the exact metadata POST /api/messages passes to saveConvo; the route now forwards the saved message's _id as appendMessageIds, which is the behavior the append path depends on.
* feat: clock format and week start preferences Times were written in whatever convention the browser locale implied, and the week always started on Sunday. Neither is right for a large part of the user base: most of Europe reads a 24-hour clock and starts the week on Monday, and a user running an English interface in a region that does either is currently given the American convention with no way to change it. Two General settings, Clock Format (System / 12-hour / 24-hour) and Week Starts On (System / Sunday / Monday). Their System branch reads the runtime locale rather than `i18n.language`, which is normalized down to a translation bundle: `en-GB` and `en-AU` both become `en`, which is exactly the regional part these two settings depend on, and reading it would report a 12-hour clock and a Sunday week to a British user. Week start is typed on the same 0-6 Sunday-first scale the schedule cadence uses rather than being narrowed to Sunday/Monday, because the System branch reports whatever the locale says and several (ar-EG, fa-IR) start the week on Saturday. Engines without `Intl.Locale.prototype.getWeekInfo` fall back to a short list of Sunday-first regions with Monday, the ISO 8601 default, otherwise: this is a display default the toggle can always override, so an imperfect fallback degrades rather than breaking. Both settings are stored per browser. They describe how this device reads a clock, which is a property of where someone is sitting rather than of their account, and a user who moves between a European desktop and a US phone wants each to read its own way. Applied to message timestamps, the schedule dialog and card, key expiry and refill dates, prompt and agent version dates, memory dates, and project chat lists. The weekday order also drives the schedule dialog's day pills and the way a weekly cadence reads back, so a wrap-around selection of Sat+Sun+Mon reads "Monday, Saturday, Sunday" in a Monday-first week instead of "Sunday, Monday, Saturday". Dropdown now names its selected value as well as its field label. `aria-labelledby` REPLACES the trigger's own text, so pointing it only at the caller's label left the selected value unannounced, which these two settings are the first consumers to hit. * fix: teach the week-start fallback the Saturday-first regions The no-week-data heuristic could only answer Sunday or Monday, folding ar-EG to Sunday and fa-IR to Monday when CLDR says both start on Saturday, and the selector offers no explicit Saturday override to recover with. It now carries CLDR's Saturday-first territories, and the UAE moves off the Sunday list to the Monday default, where CLDR put it when its weekend moved to Sat-Sun. The fallback tests delete the engine's week data for their duration, so they exercise the heuristic on every engine instead of skipping wherever getWeekInfo exists. * fix: infer likely regions for bare language tags and stop rebuilding clock formatters A runtime that reports a language-only locale (bare ar or fa) carried no region for the week-start heuristic, so those users fell to the Monday default even though maximize() knows their likely region starts the week on Saturday. The heuristic now maximizes before defaulting. The runtime locale and each locale's meridiem answer are also cached at module scope: every message timestamp mounts useClockFormat, so the uncached path built a fresh Intl.DateTimeFormat per rendered message, hundreds in a long conversation, even when the preference ignores the locale entirely. * fix: keep the Maldives on Friday in the week-start fallback CLDR's lone Friday-first territory was in neither fallback set, so dv-MV (and bare dv, which maximizes to MV) fell to Monday on engines without week data, with no Friday override in the selector to recover with. The three per-day sets consolidate into one region-to-day map. * fix: complete the Sunday-first fallback from CLDR week data The hand-picked ten Sunday-first regions left the System preference on Monday for en-IN, id-ID, bn-BD, ur-PK, th-TH and the rest of the long tail on engines without week data. The list is now every territory whose und-XX week does not start Monday per CLDR, deprecated codes included, with a note on how to regenerate it when CLDR moves a territory. * fix: mock message context across markdown test suites and prevent global plugin cache leak
A schedule's time was three dropdowns side by side: hour, minute, meridiem. That is three controls for one value, it cannot be read at a glance, and the minute list was a fixed set of four with the stored value bolted on, so a schedule already running at :07 could be kept but never chosen. They become one TimePicker: hour, minute and, where the clock format calls for one, meridiem, as scrollable columns behind a single trigger showing the selected time. An hourly cadence gets MinutePicker, the same control with its other columns dropped, so it reads as the same widget rather than a different one. Both live in packages/client with their wording passed in as props, so the primitive carries no translation keys of its own. Not `<input type="time">`: the browser owns its rendering, and it cannot be brought in line with the rest of the form. `hour12` is a required prop rather than a locale-derived guess. The app has already resolved its Clock format setting, and re-deriving the answer inside the picker would let it disagree with the summary printed beside it. The trigger names its selected value as well as its field: `aria-labelledby` replaces a button's child text, so pointing it at the label alone announced "Time" and left a screen reader user unable to tell what was selected without opening the columns and reading them. The columns are a roving-tabindex radiogroup, arrow keys wrap, and the selected row is scrolled to the middle of its column on open. The popover is deliberately not portaled. A Radix dialog sets `pointer-events: none` on the body while open, so a popover portaled out of it renders correctly but receives no clicks or wheel events, and its focus trap puts the content out of tab order too. Hour and minute are set in one change. Behind separate fields a half-applied edit could submit a time the user never picked, and the form now carries the hour as the 0-23 value the cadence stores rather than a 12-hour value plus a meridiem it has to recombine.
…15142) * feat: surface event-driven child activity * fix: keep child task aggregation documentdb-compatible * fix: address event activity review findings * test: provide markdown message context defaults * fix: report bounded child history truncation * fix: preserve current child activity state * fix: preserve durable event child activity * fix: handle missing task timestamps * fix: keep active event snapshots live * fix: preserve event activity across valid anchors * fix: close event child activity gaps * fix: preserve event activity across resume
* feat: shared empty state for side panels Bookmarks and Memories each hand-rolled the same empty state: the same bordered card, the same circular icon surface, the same title and caption sizes, written out twice. Schedules had none at all, so an account with no schedules got a bare list with nothing to explain what the panel is for. One EmptyState primitive in packages/client, taking an icon, an optional title and description, and an optional action. Bookmarks and Memories move onto it with no visual change and no copy change. Schedules gets a real empty state, and an error state with a Retry action, so a panel that failed to load offers a way out instead of looking empty. A description with no title takes the title's size rather than the caption's: where it is the only line, it IS the message. * fix: drop the create hint for roles without schedule create access The panel already hides its create button behind hasCreateAccess, but the empty state still told a USE-only viewer to create a schedule it offers no way to create. The invitation now renders only when the capability does. * fix: suppress the create hint when the quota already blocks creation A maxPerUser of 0 disables the create button on an empty list, so the empty state must not say to create one either; the hint now follows the same effective gate as the button.
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )