Commit e75835e
authored
📌 feat: Unified Pinned Section With Sidebar Drag Interactions (#15057)
* feat: unified pinned section with drag interactions and sidebar polish
- Pinned chats and favorite models/agents/specs render as one interleaved,
reorderable list persisted via users.pinnedOrder
- Drag conversations onto project rows to file them, onto Chats to unfile,
and onto the Pinned section to pin (empty section materializes as a drop
zone during a conversation drag)
- Row-level unpin button on pinned chats matching the favorites' button,
and the row action menu shares the same hover/focus treatment
- Agent Marketplace entry moved under the New Chat button in the side rail
- Bookmarks trigger sized to the search bar, search focus no longer swaps
its border color, tag filtering shows a loading state, and opening a
project shows row-shaped skeletons
* fix: add pinnedOrder to IUser type and sort imports
* fix: pinned drag threshold, unpin focus target, and marketplace link
* fix: surface failed pinned reorders and skip no-op order writes
* fix: address review findings on unified pinned section
Move the pinned-order endpoints into packages/api as TypeScript handlers and
route their URLs through the data-provider endpoint registry. Raise the entry
and key caps so a valid model favorite and an unbounded pin count can both be
persisted.
Keep the marketplace entry reachable from the mobile drawer, which is the only
sidebar surface on small screens. Merge a reorder into the stored order while a
bookmark filter hides part of the list, instead of dropping the hidden keys.
Add Alt+Arrow keyboard reordering with a live-region announcement, restore the
focus handoff when a favorite is removed, and hold the local arrangement until
the stored order catches up. Serialize overlapping order writes and discard the
optimistic value when the first one fails. Derive the tag-filter loading state
from isPreviousData rather than a flag that could stick on.
* fix: release drag while renaming and dedupe repeated favorite keys
A draggable ancestor swallows drag-select inside the rename input, so both the
row's own drag source and the pinned wrapper's release while a title is being
edited.
The favorites payload validator accepts a repeated entry, and two rows keyed
the same would collide in React, point every drag at the first copy, and fail
the order endpoint's uniqueness check. Build the pinned list keyed-unique.
* fix: address PR review bot findings
Make the model favorite key injective. Joining endpoint and model on '::'
collided for components containing the delimiter, so two distinct favorites
keyed the same and the dedupe hid one; the endpoint is now length-prefixed.
Replace the pinned-order entry cap with a total size guard. Pinning has no
membership cap and the sidebar query drains every cursor, so any count limit
rejects a legitimate list rather than bounding anything; what needs bounding is
the user document.
Let only the newest order write touch the cache. An earlier write failing
rolled back over a newer arrangement that was still in flight, leaving the UI
on a stale order while the server held the current one. A confirmed write now
takes the server's answer, and a failed newest write discards the value that
was never accepted.
Persist the favorites array only once the order write succeeds, so a rejected
write cannot leave the two stores disagreeing with the rolled-back display.
* fix: address second round of PR review bot findings
Read favorites membership at write time rather than capturing it at drag end.
A favorite added or removed while the order request was in flight was undone by
writing the older list back; only the relative order now comes from the drag.
Move the pinned-order queue to module scope. The sidebar unmounts whole
sections while a search is active, so a component-local ref let a remounted
hook start a second queue alongside a request that was still running.
Serialize project assignment per conversation. Every drop target owns its own
mutation instance and the write is an unconditional update, so two quick drops
let whichever request reached the database last decide the project.
Read the pinned-order request body null-safely: destructuring a nullish body
threw past the handler into Express's generic error path instead of returning
the intended 400.
* fix: address third round of PR review bot findings
Drop the secondary favorites write. No consumer of the favorites array reads
its order, so mirroring the arrangement there bought nothing while racing the
membership mutations that share that whole-array endpoint; pinnedOrder is the
single source of ordering.
Always merge the committed order into the stored one. The visible list is not
always the whole list, since a bookmark filter hides rows and the pinned query
publishes partial results while draining its cursor, and replacing wholesale
dropped every key it could not see.
Release the local arrangement when the write settles instead of when the stored
order matches it key for key. A pin arriving mid-request made that equality
unreachable, stranding the snapshot and hiding the new row until remount.
Abandon a queued pinned-order write if the signed-in user changed, so a
deferred request cannot write one account's order into another's document.
* fix: address fourth round of PR review bot findings
Guard the pinned-order completion callbacks on the signed-in account. The
pre-send check only covers a write that has not left yet; a response arriving
after a session change would publish one account's order into the next one's
cache, which this query never refetches on its own.
Commit a reorder only when the pinned rows handled the drop. Dragging a pin
onto a project row or into Chats sweeps the pointer across sibling rows, whose
hover handlers shift the order, so a filing action was also saving a move the
user never asked for.
* fix: address fifth round of PR review bot findings
Deselect pinnedOrder at schema level. It is display-only and allowed to grow
large, but every authentication request loads the user document, so it was
adding its whole payload to paths that never read it.
Refuse to reorder until the stored order has loaded. Merging against an absent
order and then cancelling its fetch posted only the visible keys and discarded
saved positions for good.
Prune keys only once the visible list is known to be the whole list. Merging
unconditionally grew the array until the size guard rejected every write, so
completeness now gates compaction rather than the filter flag alone.
Track every renaming row, not just the most recent. RenameForm has no blur
cancellation, so a second form can open while the first is still mounted and
the single key reconnected the first row's drag source under its open input.
Judge drop eligibility by the queued destination. A drag item carries the
project its row had when the drag began, so re-dropping a chat back where it
started while an assignment was queued read as a no-op.
Take mutation ownership from the user atom AuthContext fills on login rather
than the user query, which is still empty in that window, and never treat an
unknown owner as a match.
Restore the pinned section's 30vh cap. Matching the Projects section's 42vh put
84vh of non-shrinking content above Chats and starved it on short viewports.
* fix: address sixth round of PR review bot findings
Gate reordering and pruning on successful fetches rather than attempted ones.
A failed pinned-order GET still reports as fetched, and a failed later page of
the pinned drain still publishes partial data and stops fetching, so both gates
were opening on exactly the states they were meant to exclude.
Scope the post-unpin focus handoff to this section by its own marker. The whole
conversations pane is a labelled region too, and the project lists inside it
render the same row, where unpinning only clears the flag and the row stays
put, so focus was being moved off a row that never went away.
Clear the account tracker when the mutation hook unmounts. The sidebar drops
that section during a search, leaving a stale non-null id that also shadowed
the cache fallback, so a deferred write could pass its owner check after a
session change.
* fix: gate pruning on a delivered favorites list
A favorites fetch that exhausted its retries leaves isLoading false with the
list empty, so an otherwise complete pinned load enabled pruning and dropped
every favorite key from the stored order. useFavorites now exposes the query's
success state and the pruning gate requires it, matching the pinned-order and
pinned-membership sources.
* fix: address eighth round of PR review bot findings
Identify queued assignments by token rather than by destination. Comparing
destination values cannot tell two drops onto the same project apart, so a
B, A, B run let the first B's cleanup discard an entry the still-queued later
drops owned, and the next drop was then judged against a stale project.
Report the rename ending when a row unmounts. A pinned row removed mid-rename,
for instance by unpinning the same chat from the project list that also renders
it, left its key in the renaming set and came back permanently undraggable.
* fix: address ninth round of PR review bot findings
Stand the keyboard reorder down wherever the pointer drag does. Alt/Option plus
an arrow moves the caret word-wise inside a text field, so the shortcut was
moving and saving the whole row out from under an open rename input.
Hold a settled assignment destination until a drag item shows the new project.
The mutation's cache invalidations are not awaited, so a row still reported its
old project for a moment after the write succeeded and a quick drag back to
where it started read as a no-op.
* fix: address tenth round of PR review bot findings
Serialize project assignment inside the mutation instead of at one caller. The
drag targets, the row menu and the project dialog each hold their own instance,
so a queued drop could still overwrite a newer menu write; moving the queue
down covers every surface and lets the drag helper drop its own.
Tie the live snapshot's release to the arrangement that created it. An older
write settling mid-drag cleared the newer arrangement, snapping the rows back
to a stale order while the drag still held its own, so releasing saved an order
the user could no longer see.
* fix: address eleventh round of PR review bot findings
Move assignment destination tracking next to the queue that owns it. Keeping it
in the drag helper meant a menu or dialog move left the drag helper still
reporting its own older destination, with no row left to observe that would
ever clear it, so a later move back was rejected for good.
Resolve a conversation's project from the write the mutation confirmed rather
than from whichever list rendered the row. The same chat appears in the Pinned
section and its expanded project, and those caches refresh independently, so
observing one of them was never a sound way to decide the other had caught up.
The conversation cache is written synchronously on success, which makes the
lazy self-clearing and its settled flag unnecessary.
* fix: cancel an in-flight conversation refetch before confirming an assignment
Navigation invalidates the exact conversation query, so a refetch that read the
old project can be in flight when an assignment resolves and land afterwards,
reverting the move in the cache that now answers where a conversation lives.
* fix: keep queued pinned-order writes attributable across an unmount
Refuse a queued write only when a different account is positively established.
Clearing the identity on unmount stopped stale attributions but stranded work
that belonged to the same account, since the sidebar drops the section that
owns this hook whenever a search is active, and it denied that work its
rollback as well.
Correct the cache from the request rather than from onError. The observer
callbacks do not run once that section unmounts, so an order the server never
accepted could survive on a query that refetches on nothing.
* fix: publish the session identity from the user atom
Queued pinned-order writes were attributed by a tracker that only the pinned
section's hook updated, and the sidebar unmounts that section during a search.
A retained value went stale across a sign-in, and login clears the user query
before the next account's fetch resolves, so a write queued by the previous
account could pass its own check and post on the new account's credentials.
A Recoil atom effect publishes the identity instead: it runs wherever the atom
is set, including sign-out, so attribution comes from the state itself rather
than from a component that happens to be mounted. That makes a signed-out
session foreign to every owner and lets the check be a strict comparison again,
rather than a rule that had to guess when identity was merely unreadable.
* fix: discard reorders that ended on a refused external target
A drop the pinned rows did not handle usually means a reorder, but a project
row or the Chats section refusing one reports the same, so dragging a chat onto
the project it already belongs to was saving the incidental shift its pointer
caused on the way out.
The rows and those targets now report their hovers, so the last one under the
pointer settles it, which also allows for a drag that strays over a project and
returns to reorder after all.
* fix: attribute queued writes to the credentials they will travel with
Publishing the account from React state, whether an effect or a Recoil atom
effect, always lagged the credential change: setUserContext installs the new
Authorization header in one synchronous call, and a write queued by the
previous account and sent in that gap read its own owner back and went out as
the new one. A test against the atom effect showed onSet is deferred too, so
the atom was no better than the effect it replaced.
The header is the thing that decides who the server sees, so ownership is read
from it. The id claim is compared rather than the token, since a silent refresh
rotates the token for the same account and discarding that work would strand
something the user is still entitled to.
Leaves AuthContext and the user atom untouched.
* fix: attribute a reorder to the account that started it
onMutate awaits cancelQueries before the request is built, and the mutation
function was reading the session again afterwards, so credentials turning over
inside that gap sent one account's order as the next. The owner is now recorded
in onMutate's first synchronous statement and carried on the order itself, and
the optimistic write is skipped when the session moved on, since that cache is
shared and showing an order to the wrong account is the same mistake as sending
it.
Record a pinned hover before the guards rather than after. Hovering the dragged
row is a no-op for ordering but still means the pointer is inside the list, so
a drag that wandered onto a project and returned to its own position was being
discarded as a filing action.
* fix: address a further round of PR review bot findings
Mark a hover anywhere in the pinned section as internal. The header and the
padding around the rows are part of the list, so a drag that wandered onto a
project and came back to rest on one of them ran no row hover and was discarded
as a filing action.
Hold off pruning while a favorites write is unsettled. The earlier GET's
success stands through an optimistic removal, so pruning then dropped the
ordering key of a write that had not landed, and a failed one came back without
its saved position.
Declare the reorder shortcut on the elements that take focus. The section's
written hint is skipped by anyone tabbing straight to a row, leaving the only
non-pointer way to reorder undiscoverable.
* fix: reach a real New Chat control, and let the shortcut prop through the memo
The unpin fallback looked for a test id no component carries, so once the last
pinned row unmounted focus fell to the document rather than to New Chat. It now
tries the controls that exist, in sidebar order. The test that covered this had
fabricated the missing id, so it proved nothing.
Convo is memoized on conversation fields and isGenerating alone, so the
shortcut appearing once the pinned order loaded never reached the element that
announces it. The comparator now accounts for it.
* fix: identify OpenID sessions, and see favorites writes from every hook
With OpenID token reuse the bearer is the provider's own id_token, which names
its subject as sub under an issuer and carries no LibreChat id, so every such
session resolved to nobody and two different accounts compared equal. That is
the case the ownership guard exists for, so it read as safe while being exactly
wrong. Sessions are identified by whatever the credential offers now, and an
owner that cannot be identified is foreign to everything including itself.
Count favorites writes across the query client rather than from one observer.
A favorite row runs its own useFavorites, so a removal started there was
invisible to the pinned section and its ordering key could still be pruned.
* fix: close the assignment cache gap, and let a failed drain be retried
The assignment released its pending destination as soon as the request settled,
while the cache write waited on an awaited cancellation in onSuccess. A drag
started in that gap saw neither, fell back to the stale row and refused a valid
drop. The write now happens in the request, before the entry is released, which
also survives the owning component unmounting.
Also clear the pagination guard when the Chats section collapses. A drain that
exhausted its retries leaves the conversations array unchanged, so the guard
barred every later attempt and the remaining chats stayed unreachable for the
session. Reopening the section now tries once more. That guard came from #14860
rather than this branch, so it can be split out if preferred.
* fix: compose the marketplace link, and keep focus on a surviving row
The marketplace control was a raw link with restated appearance classes, and
in restating them it left out the focus ring entirely, so keyboard focus on it
was invisible. It composes through the shared button now, which carries the
ring, the hover fill and the theme timing.
Unpinning from a project list leaves the row in place but removes the pin badge
that had focus, and the pinned-section successor search deliberately returns
nothing there, so focus fell to the document. It moves to the row's own link.
Reconcile the pinned order on window focus and reconnect. It is one shared
per-user record, so a second tab would otherwise show a stale arrangement and
its next reorder would post a full array undoing the other tab's.
* fix: keep a rename from restoring a conversation's other fields
A rename request carries only a title, but its response is the whole
conversation as the server saw it, and writing all of that back restores the
pre-request value of every other field. An assignment confirmed while the
rename was in flight was therefore reverted in the cache that now answers which
project a conversation belongs to, and a drag back to the original project read
as a no-op.
Only the title and its timestamp are taken from the response. Cancelling
queries cannot help here, since this is another mutation's write rather than a
fetch.
* fix: compose the unpin badge through the shared row-action button
The badge restated a reusable icon control's sizing, radius, hover and focus
classes. The client package already had a row-action variant and an icon-xs
size that match it exactly, so it uses those instead of its own copy.
* fix: reconcile before reordering, and scope assignments to their session
A reorder stayed enabled while the order's focus refetch was in flight, so
returning to a tab could cancel that refetch and post the arrangement it was
about to replace. Pruning had the matching gap: the pinned list keeps its data
fresh for five minutes, so a tab whose order had reconciled could prune against
membership that had not, dropping the position of a conversation another tab
pinned. Both now wait for data that is settled and not stale.
Assignments carry the account that started them, like the order writes already
did. Conversation ids are per-user, so a queued one travelling with the next
account's credentials would act on whatever that id names over there, and its
answer would describe one account's conversation to another in a cache that is
not scoped by user.
The row's overflow trigger derives from the shared row-action variant too, so
the two actions in a row stop keeping separate copies of the same recipe.
* fix: reject foreign assignment results, and share the section header
An assignment settling after a session change returned normally, so the success
path still ran: the caller reported success and the response could be applied to
the next account's conversation, since ids repeat across accounts. It rejects
now, so nothing downstream treats it as a result.
The collapsible section header existed as three identical copies, Chats,
Projects and Pinned, and this branch added the third. It moves into the shared
button as a section-header variant and all three compose from it.
* fix: let favorites membership reconcile between tabs
The favorites query disabled focus, reconnect and mount refetches, so a second
tab kept its old list indefinitely. Its pruning gate waits for a fetch, and
none was ever going to happen, so a reorder there could delete the saved
position of a favorite the first tab had added. It reconciles on focus and
reconnect now, like the order it is arranged alongside.
* fix: hold reconciling refetches off while a write is in flight
Enabling focus and reconnect refetches on the order and favorites queries let
one start after onMutate's single cancellation, read the pre-write state and
land after the write, putting the old value back. Both now decline those
refetches while a write for that resource is outstanding, keeping the cross-tab
reconciliation itself.
The check asks the query client at the moment the event fires rather than
reading useIsMutating: that hook only reaches the query's options on the next
render, which is a race against the very event being gated. A test that
dispatches focus mid-write fails without it.
* fix: release abandoned assignments and gate pruning on current membership
An assignment rejected before entering the try block kept its pending entry
forever, so a later session read a destination that was never sent. The
ownership check now sits inside the block that releases it, and it runs again
after the conversation query cancellation resolves, since the session can turn
over while that await is pending.
Pruning the pinned order now requires membership at least as current as the
order it is pruned against. The pinned query holds its data for five minutes
while the order has no such window, so focusing a tab reconciled the order
alone and the next reorder dropped the key of a conversation another tab had
pinned in between.
The assignment spec resets its mocks between tests and gives each test its own
conversation id, so one test's unconsumed mock and leftover queue entries no
longer decide the next test's result.
* fix: address the sixth round of review findings on the pinned section
The section header recipe asked for the shared button variant without a size,
so CVA emitted the default `h-10 px-4` after it and the merge kept it, putting
a 40px control in a 32px header row. The variant opts out of default sizing
through a compound now, since a heading is sized by its own text.
Restore the search field's focus border. Removing it left keyboard focus
signalled only by a background change that hover already uses, so the field
had no distinguishable focus state at all.
An assignment can reach the database before a concurrent rename and still
answer after it, carrying a conversation that predates the rename. Only the
project field is merged into the cached conversation and published to the
active one now, matching what the rename handler already does.
An order refetch that starts mid drag no longer lets the drop post an array
built from the order that refetch is about to replace: the arrangement is held
and written once the fresh order lands. Reorder writes superseded by a newer
arrangement are skipped rather than sent, so holding the shortcut costs two
writes instead of one per intermediate position. The order and favorites
queries reconcile on mount as well, since a sidebar search unmounts the
section and a query with no observer never sees the focus event.
* fix: preserve pinned order until favorites membership catches up
* fix: reconcile pinned membership and preserve auth and focus state
* fix: preserve reconciled keys during active pinned arrangements
* style: sort pinned order regression imports
* fix: reconcile favorite rollbacks before pinned-order pruning1 parent 523e800 commit e75835e
55 files changed
Lines changed: 5184 additions & 1514 deletions
File tree
- api/server/routes
- client/src
- components
- Conversations
- ConvoOptions
- __tests__
- UnifiedSidebar
- __tests__
- mobile
- __tests__
- data-provider
- Projects
- __tests__
- __tests__
- hooks
- locales/en
- utils
- __tests__
- packages
- api/src/favorites
- client/src/components
- data-provider/src
- data-schemas/src
- schema
- types
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
3 | 8 | | |
4 | 9 | | |
5 | 10 | | |
| |||
9 | 14 | | |
10 | 15 | | |
11 | 16 | | |
12 | | - | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
13 | 25 | | |
14 | 26 | | |
15 | 27 | | |
| |||
19 | 31 | | |
20 | 32 | | |
21 | 33 | | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
22 | 42 | | |
23 | 43 | | |
24 | 44 | | |
| |||
28 | 48 | | |
29 | 49 | | |
30 | 50 | | |
| 51 | + | |
| 52 | + | |
31 | 53 | | |
32 | 54 | | |
33 | 55 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
5 | | - | |
6 | 6 | | |
| 7 | + | |
7 | 8 | | |
8 | 9 | | |
| 10 | + | |
9 | 11 | | |
10 | | - | |
11 | | - | |
12 | | - | |
13 | | - | |
14 | | - | |
15 | | - | |
16 | | - | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
17 | 18 | | |
18 | 19 | | |
19 | 20 | | |
| |||
39 | 40 | | |
40 | 41 | | |
41 | 42 | | |
42 | | - | |
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
| |||
94 | 94 | | |
95 | 95 | | |
96 | 96 | | |
| 97 | + | |
| 98 | + | |
97 | 99 | | |
98 | 100 | | |
99 | 101 | | |
100 | | - | |
| 102 | + | |
101 | 103 | | |
102 | 104 | | |
103 | 105 | | |
104 | | - | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
105 | 112 | | |
106 | 113 | | |
107 | | - | |
| 114 | + | |
108 | 115 | | |
109 | 116 | | |
110 | 117 | | |
| |||
142 | 149 | | |
143 | 150 | | |
144 | 151 | | |
145 | | - | |
146 | 152 | | |
147 | 153 | | |
148 | 154 | | |
| |||
157 | 163 | | |
158 | 164 | | |
159 | 165 | | |
160 | | - | |
161 | 166 | | |
162 | 167 | | |
163 | 168 | | |
164 | 169 | | |
165 | | - | |
166 | 170 | | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
167 | 190 | | |
168 | | - | |
169 | 191 | | |
170 | 192 | | |
171 | 193 | | |
172 | 194 | | |
173 | 195 | | |
174 | 196 | | |
175 | | - | |
176 | | - | |
177 | 197 | | |
178 | 198 | | |
179 | 199 | | |
180 | 200 | | |
181 | 201 | | |
182 | 202 | | |
183 | 203 | | |
184 | | - | |
185 | | - | |
186 | | - | |
187 | | - | |
188 | | - | |
189 | | - | |
190 | | - | |
191 | | - | |
192 | 204 | | |
193 | 205 | | |
194 | 206 | | |
| |||
205 | 217 | | |
206 | 218 | | |
207 | 219 | | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
208 | 232 | | |
209 | 233 | | |
210 | 234 | | |
| |||
225 | 249 | | |
226 | 250 | | |
227 | 251 | | |
228 | | - | |
229 | | - | |
230 | | - | |
231 | | - | |
232 | | - | |
233 | 252 | | |
234 | 253 | | |
235 | 254 | | |
| |||
241 | 260 | | |
242 | 261 | | |
243 | 262 | | |
244 | | - | |
| 263 | + | |
245 | 264 | | |
246 | 265 | | |
247 | 266 | | |
| |||
258 | 277 | | |
259 | 278 | | |
260 | 279 | | |
261 | | - | |
262 | | - | |
263 | | - | |
264 | 280 | | |
265 | | - | |
266 | | - | |
| 281 | + | |
267 | 282 | | |
268 | 283 | | |
269 | 284 | | |
| |||
277 | 292 | | |
278 | 293 | | |
279 | 294 | | |
280 | | - | |
281 | | - | |
282 | | - | |
283 | | - | |
284 | | - | |
285 | | - | |
286 | | - | |
287 | | - | |
288 | | - | |
289 | | - | |
290 | | - | |
291 | | - | |
292 | | - | |
293 | | - | |
294 | | - | |
295 | | - | |
296 | 295 | | |
297 | 296 | | |
298 | 297 | | |
| |||
345 | 344 | | |
346 | 345 | | |
347 | 346 | | |
348 | | - | |
349 | | - | |
350 | | - | |
351 | | - | |
352 | | - | |
353 | | - | |
354 | | - | |
355 | | - | |
356 | 347 | | |
357 | | - | |
358 | 348 | | |
359 | 349 | | |
360 | | - | |
| 350 | + | |
361 | 351 | | |
362 | 352 | | |
363 | 353 | | |
| |||
371 | 361 | | |
372 | 362 | | |
373 | 363 | | |
| 364 | + | |
374 | 365 | | |
375 | 366 | | |
376 | 367 | | |
377 | 368 | | |
378 | 369 | | |
379 | 370 | | |
380 | 371 | | |
381 | | - | |
| 372 | + | |
382 | 373 | | |
383 | 374 | | |
384 | 375 | | |
| |||
401 | 392 | | |
402 | 393 | | |
403 | 394 | | |
404 | | - | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
405 | 399 | | |
406 | 400 | | |
407 | 401 | | |
408 | 402 | | |
409 | 403 | | |
| 404 | + | |
410 | 405 | | |
411 | 406 | | |
412 | 407 | | |
| |||
0 commit comments