Dev2026 07 07 - #106
Merged
Merged
Conversation
"Map arrow still gets stuck on unbackgrounding, readouts are fine" —
plus, on asking, the map still pans on GPS. That combination rules out
everything the last few days were spent on: the sensors deliver, the
state updates, the canvas repaints.
What is left is the one path with none of those properties. The arrow is
drawn by osmdroid from a value the AndroidView update block copies out
of the bearing state, and that block runs on recomposition — while the
GPS follow is coroutine-driven (snapshotFlow) and needs none. A block
that stops re-running therefore looks precisely like this: readouts
track, map pans, arrow holds whatever it was last handed.
I could not reproduce it here. A HOME cycle and a 45-second
backgrounding behind another app with send-trim-memory RUNNING_CRITICAL
both left the arrow tracking (86k-92k pixels changed across a heading
change, against a 71k baseline), so the emulator has nothing more to
say and guessing further would be guessing.
So the readout gets the missing fact:
🗺 arrow 24s @307.7° Δ0.0°
when the overlay was last handed a bearing, which one, and how far the
state has moved since. Δ is the tell rather than the age — a still phone
legitimately shows a climbing age at Δ0, because nothing recomposed. A
large Δ says the drawing stopped, not the sensing, and points at the
update block instead of the sensor stack.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JjTMBKnJbfJ4cJmBitR3Sq
"We really need a better way of visually indicating that the interval mode is about to be started" — and the device showed why every way so far had failed: the armed state lived only in the ladder's head label, and at ordinary split positions the 280 dp track is taller than the capture pane, so that label renders OFF-PANE. The user was not missing the signal; the signal was not on screen. Two changes. The fact itself was unrenderable: whether release starts a run was a local variable inside the gesture loop (overSlider), invisible to composition by construction. Hoisted into state (armedStop: what release does now, null = cancel). And the announcement moved to the one place that cannot be clipped: the shutter button previews its own future. Armed for a run it wears the run's green with "▶ Ns"; armed at the top stop, video's red with "⏺ REC"; over the button, blue with "release: cancel" under it. The verdict line under the circle says it in words — release: start 4s run / record / cancel. The ladder head keeps only the compact stop label. Verified on the emulator mid-gesture (input motionevent DOWN/MOVE, no release): all three states screenshot-confirmed, and the VIDEO stop remains reachable while the track top is clipped — the gesture holds pointer capture, so sliding to the screen's top edge still lands on it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JjTMBKnJbfJ4cJmBitR3Sq
Fast repeated swipes advanced zero photos. The original solved exactly this, and the mechanism was sitting in swipe2d.ts:141-154: a successful release parks its completion handler in pendingTransitionListener, and startDrag calls it SYNCHRONOUSLY with a synthetic transitionend before doing anything else — the turn the animation was carrying is applied now, the grid rests, and the new drag starts clean on the new front photo. Our commit() was the opposite: animateTo → turnTo → snapTo in one coroutine, so the second gesture's snapTo (Animatable's mutex: a new caller cancels the current one) killed it INSIDE animateTo and turnTo never ran. The first swipe was not delayed, it was destroyed — and the second one then measured its drag against the un-turned neighbours, so two swipes moved one photo at best. Ported: commit() records what it is carrying (pending: turn + photo, the original's listener made data), onDragStart and the chevrons fast- forward it — apply the turn, rest the grid, then proceed. pending is cleared between animateTo returning and the next suspension point, so a touch landing in that window cannot apply the turn twice. Also unkeyed the gesture handler from the neighbours: pointerInput(state.left, ...) restarted detectDragGestures — killing the live drag — every time a turn landed mid-gesture, which is precisely when the fast-forward fires. The lambdas read `state` through the State delegate, so they see current neighbours without a restart; only the pane size remains a key. Verified on the emulator via the round-trip invariant: two fast left swipes (second landing inside the first's 300 ms animation), two slow right swipes → back at the starting uid (c3b1dd9b → eb5197d3 → c3b1dd9b), and the fast pair confirmed as two real steps (one slow swipe from the same start lands elsewhere). A lost swipe cannot close that loop. The lesson is recorded in docs/tauri-viewer-ui-contract.md's swipe section: the fast-forward IS part of the swipe contract, not an optimisation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JjTMBKnJbfJ4cJmBitR3Sq
The gutter goes 48 -> 64 dp and the track line 16 -> 22 dp. This control is read mid-gesture, at arm's length, from the corner of the eye — the defaults were sized for a control you look AT. The track needed measuring, not eyeballing: a first pass set 12 dp, which LOOKED like the improvement it claimed to be in the diff while actually thinning the line — this Material3's default track is already 16 dp (41 px on the test device). Pixel-measured before committing: 41 px -> 57 px, matching 22 dp exactly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JjTMBKnJbfJ4cJmBitR3Sq
"The whole problem was that there was no indication that it was the area left of the button, i kept trying to target the track exactly." The gesture has always accepted any point left of the shutter (pos.x < circle.left) — the track is a picture, not the hit-box — and the comment in the code even says why: "a mid-gesture thumb is not a precision instrument". But the screen showed only the thin line, so the user did precision work the code never asked for, and the widening of the track two commits ago was treating the symptom. While the slider is open, the entire catch zone — pane's left edge to the button — now wears a wash, tinted by the armed state: faint white while release would cancel, the run's green or video's red once armed. The affordance is the hit-box. The slider-visible/armed/circle state moved from the shutter cluster's scope to the pane's, since the wash lives outside the cluster. Verified on the emulator: a finger at (200,480) — nowhere near the track — armed a 1 s run with the whole zone green; back over the button, faint white and "release: cancel". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JjTMBKnJbfJ4cJmBitR3Sq
Prior art, per review: the original's DebugOverlay is a mess, but it has a close button in its header (closeDebug, DebugOverlay.svelte:50). The readout here shipped without one, so making it go away meant a trip through Settings. The ✕ writes the same persisted setting the Settings switch reads — one fact, two controls. Placed at the START of the row, not the end, after the end placement failed on device: the panel re-measures on every half-second tick (the age strings change width), so a button positioned after the longest line drifts as the text ticks — a moving target. The panel's left edge is anchored; that corner stands still. (The isolated compose test passes in either layout because its lines are static — it guards the wiring and the hit region, not the drift.) Verified on the emulator: tap on the ✕ dismisses the readout and persists show_geo_debug=false. The verification script's dump had to delete the stale file first — uiautomator dump can fail silently and leave the previous dump to be re-read, which cost one round of tapping phantom coordinates. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JjTMBKnJbfJ4cJmBitR3Sq
Field report: pitch-black room, Sports @ 1/2000 — "still says just 1/2000 and takes pitch black photos". Two separate things, and the display gap made them inseparable. The arithmetic is innocent, and now provably: DarkRoomExposureTest runs the exact metering loop the phone runs — each dark frame credited to the current plan's own exposure, plan recomputed from the meter — and from either start (rule engaged in the dark, or lights going out after) it slides to the Sports floor within ~30 frames and pins there at max gain. A third test pins the contract itself: Sports NEVER goes slower than 1/125, however dark — 1/125 @ ISO 3200 in a truly black room IS a black photo, by design; the mode that photographs a dark room is Auto. What was broken is that a working slide and a dead one looked identical: the ⚡ button printed the RULE's target, always, and the resolved plan lived only inside the open menu. The button now appends where the plan landed when it differs — "🏃1/2000→1/125" — so the slide is visible at a glance, and its absence in the dark now MEANS something (a dead metering loop, worth a logcat). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JjTMBKnJbfJ4cJmBitR3Sq
User-decided: an interval run is a walking or driving shoot, where motion blur is the failure mode and Sports is the rule built for exactly that — a fast target while the light allows it, a graceful retreat to 1/125 when it does not, instead of Auto's smear or a pinned target's black frames. Scoped tightly to what "default" means. It engages only when the rule is Auto at the moment the run starts — Pin, Floor or Sports picked by hand is the user's choice and survives untouched. It uses the remembered target and bias, not fresh defaults. And it lasts exactly the run: engaged at the top of the run effect, stood down in its finally (which the effect's cancellation routes both the stop-tap and leaving the screen through), and if the user changed the rule mid-run the stand-down keeps its hands off — identity comparison, so even re-picking identical Sports values counts as an explicit choice. Verified on the emulator through the full cycle: "⚡ Auto" before, "⚡ 🏃1/500→1/1264" during the run (the auto-engaged rule and the plan-divergence arrow from the previous commit, composing), "⚡ Auto" after the stop tap. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JjTMBKnJbfJ4cJmBitR3Sq
The original has it in the stats header (RetryUploadsButton
global={true}, DevicePhotoStats.svelte:105); this port only had the
per-card retry, so "make everything go out, now" had no button.
Placed beside the stats line, and only when it can do something: shown
when photos are pending or failed AND the gate is open (auto-upload on,
logged in). With work waiting but the gate shut, the original's
sentence shows instead — a force button the gate would silently
swallow is a trap.
"Force" is real and was already plumbed: the retry_button trigger
bypasses the failed rows' exponential backoff (isEligibleNow) and the
wifi-only constraint (decideUploadSchedule's documented bypass), so it
uploads now, on whatever network is there.
Verified on the emulator end to end: network cut, photo captured (26
photos · 1 pending), button appeared; network restored — at which point
the parked NetworkType job fired on its own and drained the photo
before the tap, which is the wifi-return trigger doing its job — and
the tap's reconcile [retry_button] is in the log finding a clean queue.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JjTMBKnJbfJ4cJmBitR3Sq
"Pending" folded three different situations into one number — waiting for a drain, bytes in flight, and uploaded-with-the-server-still- processing — and the last reads as "stuck" precisely when everything is working: the forced upload landed, the server said "authorized", and the line went on saying "1 pending" (user-raised). A queue display is only reassuring if it moves when the queue does. StatusCounts now carries waiting / uploading / processing / done / failed (the DAO always had the granular queries; the lump was ours), and the line shows only the non-zero stages — "26 photos · 26 uploaded" on the common all-done day, the full walk while a batch drains. The Upload-now button's condition sharpens with it: actionable = waiting + failed. It no longer offers to force what is already in flight or already on the server. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JjTMBKnJbfJ4cJmBitR3Sq
The per-card "Retry uploads" button was the global drain wearing a per-photo costume — it sat on a card and drained the whole queue (the original's does the same: RetryUploadsButton takes a photo prop, logs its id, and invokes the global retry_uploads command). What the user wants from a button on a photo is that photo, now. The drain has carried a targeted mode all along (doWorkInternal's photoId — fetch that row, bypass the auto-upload gate), but startManualUpload was an empty stub, and finishing it surfaced why: the targeted loop never ends. It refetches the SAME row each iteration, and the claim only checks equality with the status it was selected under — so after a successful upload it would claim the row again in "processing", drain it in duplicate, reach "completed", claim THAT, and so on forever. Two exits added: a row that is not waiting (pending/failed) is nothing to force, and one pass over one photo ends the loop, success or failure alike — failure especially, since retry_button bypasses the backoff that would otherwise space the spins. The gate is login only. The targeted path ignores the auto-upload toggle by design — a manual "upload THIS" is not automatic uploading, and someone who keeps auto-upload OFF and pushes photos out by hand is exactly who a force button serves. (The original gated its button on the toggle, correctly for ITS button: a global drain is what the toggle governs. Ours diverges with the mechanism.) Verified on the emulator, both halves: offline force = exactly one attempt, row lands in failed, no spin; online force of that failed row = targeted drain in the log with the row's id, retried 30 s after failing (the 1-minute backoff provably bypassed), success, stats walking waiting -> failed -> processing. The header's global button stays where it is — it matches the original's placement, and "everything, now" remains a real errand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JjTMBKnJbfJ4cJmBitR3Sq
…hillview into dev2026-07-07
parseInstant handles all the app's instant shapes (ISO-Z, python microseconds, offset-less-as-UTC, epoch ms, Date); formatters render '2026-08-24 15:45[:12]' in the viewer's zone, plus UTC and zoned variants and viewerZoneInfo for zone-transparency affordances. formatUtcDateTime (admin surfaces) switches from DD-MM-YYYY to ISO order. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HA5gbvvUtDKBMKDQJ7cJQ
Captured now renders ISO 24-h in the viewer's zone; an inline ⓘ (touch-friendly, title-attr mouseover on desktop) expands to the corresponding UTC and a note naming the device zone — and that it is not the capture location's local time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HA5gbvvUtDKBMKDQJ7cJQ
All absolute toLocale* renderings now go through dateUtils; the photo detail page keeps its explicit zone suffix via formatDateTimeZoned, and naive backend strings are read as UTC everywhere (parseInstant). Humanized forms stay: activity day headers, users-list relative dates (its >7d fallback is now ISO). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HA5gbvvUtDKBMKDQJ7cJQ
formatLocalDate/Time switch from locale DateFormat to fixed yyyy-MM-dd / HH:mm:ss patterns (device-local zone), so both apps render timestamps identically. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HA5gbvvUtDKBMKDQJ7cJQ
…hillview into dev2026-07-07
…hillview into dev2026-07-07
There was a problem hiding this comment.
🔵 Needs a closer look
The PR spans multiple subsystems (web viewer rendering/printing, Android upload/metering logic, Docker/runtime changes, and dependency tooling), making it high-impact and better suited for final human validation.
Pull request overview
This PR is a cross-cutting UX + infrastructure update across the web zoom viewer, frontend2 capture/map UI, Android upload plumbing, and repo dependency/ops tooling.
Changes:
- Adds a “print view” mode to the zoom viewer (frozen rendering + QR share link + print-specific overlay palettes/layering) with Playwright coverage and documentation.
- Standardizes date/time display across the web UI via a new ISO-oriented
dateUtilsand updates multiple routes/components to use it. - Improves frontend2 capture/map behavior and debuggability (interval shutter UX, arrow-staleness debug line + close button, exposure metering credit fix with new tests) and introduces tooling/docs for dependency cooloff + a parked Postgres Alpine migration runbook.
File summaries
| File | Description |
|---|---|
| shared/zoomview/labelPaint.ts | Adds label palettes and split-pass rendering (leaders vs pills) for print/layered canvases. |
| shared/terrain/labelPills.ts | Adds palette/leader-width styling and two-pass paint ordering for sky pills (print support). |
| shared-kt/src/cz/hillview/plugin/PhotoUploadManager.kt | Adds manual single-photo upload trigger on a foreground path. |
| shared-kt/src/cz/hillview/plugin/PhotoUploadLogic.kt | Adds targeted-mode guards to prevent infinite retry loops / duplicate drains. |
| scripts/deps.py | New repo-wide dependency audit/refresh helper with cooloff policy enforcement. |
| frontend2/shared/src/jvmTest/kotlin/cz/hillview/map/MapOverlayUiTest.kt | Adds UI test ensuring geo debug overlay can be dismissed in-place. |
| frontend2/shared/src/jvmShared/kotlin/cz/hillview/devicephotos/Format.jvmShared.kt | Switches JVM date/time formatting to fixed ISO patterns for consistency. |
| frontend2/shared/src/jvmMain/kotlin/cz/hillview/devicephotos/DevicePhotos.jvm.kt | Updates desktop stub for expanded upload status counts and targeted retry API. |
| frontend2/shared/src/commonTest/kotlin/cz/hillview/geo/GeoDebugTextTest.kt | Adds coverage for new “arrow last set” debug line behavior. |
| frontend2/shared/src/commonTest/kotlin/cz/hillview/capture/ShutterGateTest.kt | Expands capture/exposure label tests and adds metering-credit precedence tests. |
| frontend2/shared/src/commonMain/kotlin/cz/hillview/viewer/ViewerPane.kt | Improves gesture behavior (fast-forward in-flight transitions; pointer handler keying). |
| frontend2/shared/src/commonMain/kotlin/cz/hillview/map/MapOverlayUi.kt | Adds dismiss button to geo debug panel and passes debug lines/close callback. |
| frontend2/shared/src/commonMain/kotlin/cz/hillview/geo/GeoDebugText.kt | Adds staleness diagnostics for map arrow vs current bearing. |
| frontend2/shared/src/commonMain/kotlin/cz/hillview/devicephotos/DevicePhotosScreen.kt | Refines upload status presentation and adds global/targeted “upload now” actions. |
| frontend2/shared/src/commonMain/kotlin/cz/hillview/devicephotos/DevicePhotos.kt | Splits upload counts by stage and adds targeted retry API to browser interface. |
| frontend2/shared/src/commonMain/kotlin/cz/hillview/capture/PhotoCapture.kt | Adds meterCreditedExposure and improves exposure label to show “landed” value. |
| frontend2/shared/src/commonMain/kotlin/cz/hillview/capture/CaptureScreen.kt | Interval shutter UX improvements and auto-engage Sports for interval runs under Auto. |
| frontend2/shared/src/androidMain/kotlin/cz/hillview/map/MapScreen.android.kt | Records when/what bearing the overlay arrow was last handed; wires close-debug setting. |
| frontend2/shared/src/androidMain/kotlin/cz/hillview/devicephotos/DevicePhotos.android.kt | Implements targeted photo upload retry via PhotoUploadManager.startManualUpload. |
| frontend2/shared/src/androidMain/kotlin/cz/hillview/capture/PhotoCapture.android.kt | Fixes SceneMeter credit precedence via meterCreditedExposure. |
| frontend2/shared/src/androidHostTest/kotlin/cz/hillview/capture/SceneMeterLoopTest.kt | New end-to-end synthetic loop test guarding the “frozen harvest” precedence bug. |
| frontend2/shared/src/androidHostTest/kotlin/cz/hillview/capture/DarkRoomExposureTest.kt | New numeric tests for Sports floor behavior in darkness scenarios. |
| frontend2/scripts/emulator.sh | Documents watchdog coverage for emulators started outside the wrapper. |
| frontend2/scripts/emulator-watchdog.py | New watchdog to clamp/stop forgotten emulators (systemd timer driven). |
| frontend/tests-playwright/zoomview-print.spec.ts | New Playwright coverage for print view behavior and single-page PDF output. |
| frontend/tauri-plugin-hillview/Cargo.toml | Updates Tauri dependency version. |
| frontend/src/routes/users/+page.svelte | Switches date display to shared ISO/instant parsing utilities. |
| frontend/src/routes/photos/+page.svelte | Switches log time formatting to shared formatter. |
| frontend/src/routes/hidden/+page.svelte | Switches timestamp formatting to shared formatter. |
| frontend/src/routes/device-photos/+page.svelte | Switches date/time display to shared ISO formatters. |
| frontend/src/routes/account/+page.svelte | Switches date formatting to shared formatter. |
| frontend/src/lib/shareUtils.ts | Factors share URL construction + adds QR-friendly slug compaction helper. |
| frontend/src/lib/shareUtils.test.ts | Adds tests for QR slug compaction. |
| frontend/src/lib/photoUtils.ts | Adds captured_at viewer-zone details + UTC companion details. |
| frontend/src/lib/photoUtils.test.ts | Adds tests for captured_at details extraction (ISO-Z + epoch ms). |
| frontend/src/lib/photoDisplay.ts | Replaces ad-hoc parsing/formatting with unified dateUtils helpers. |
| frontend/src/lib/dateUtils.ts | Introduces site-wide ISO display convention + instant parsing utilities. |
| frontend/src/lib/dateUtils.test.ts | Adds tests for instant parsing + ISO formatting shapes/UTC formatting. |
| frontend/src/lib/components/TimelinePanel.svelte | Uses shared formatter for timeline timestamps. |
| frontend/src/lib/components/PhotoItem.svelte | Removes local date formatting helpers in favor of shared formatter. |
| frontend/src/lib/components/PhotoInfoWindow.svelte | Adds timezone transparency UI for captured time (viewer zone + UTC detail toggle). |
| frontend/src/lib/components/OpenSeadragonViewer.svelte | Adds print view mode (freeze, QR, layer order, print CSS, fine zoom). |
| frontend/src/lib/components/DebugOverlay.svelte | Uses shared date formatting for captured_at. |
| frontend/src/lib/components/DebugMode1.svelte | Uses shared time formatting for “current time” and sensor timestamps. |
| frontend/src/lib/components/CameraCapture.svelte | Uses shared date/time formatting for overlay timestamps. |
| frontend/src/lib/compass.svelte.ts | Uses shared time formatting for sensor timestamp field. |
| frontend/src/lib/build-info.ts | Uses shared formatter for build time display. |
| frontend/src-tauri/Cargo.toml | Updates Tauri dependency version. |
| frontend/src-tauri/Cargo.lock | Lockfile update reflecting dependency bumps (notably Tauri). |
| frontend/package.json | Updates key frontend deps and expands overrides set; adds bun cooloff config file. |
| frontend/bunfig.toml | Adds bun dependency cooloff policy configuration. |
| docs/zoomview-print.md | Documents zoom viewer print view behavior, constraints, and verification steps. |
| docs/tauri-viewer-ui-contract.md | Documents the “fast-forward interrupted animation” gesture contract. |
| docs/postgres-alpine-migration.md | Adds parked runbook for migrating Postgres to Alpine + ICU collation rationale. |
| docs/frontend2-status.md | Adds debug writeup for “map arrow freeze” investigation and interval ladder UX notes. |
| docs/frontend2-capture-backlog.md | Records capture backlog fix details for exposure credit precedence bug. |
| docker-compose.yml | Adds parked Postgres Alpine migration scaffolding + bumps Umami digest with security note. |
| CLAUDE.md | Adds dependency tooling commands and links to new docs. |
| backend/worker/local_photos.py | Expands symlink resolution to walk component-by-component with hop limits. |
| backend/worker/Dockerfile | Trims system deps, switches to uv copy-in, and removes uv from runtime image. |
| backend/scripts/pg_rehearse_alpine.py | Adds rehearsal script to validate dump/restore + collation parity before migration. |
| backend/scripts/fix_frontend2_captured_at_from_filename.sql | Adds corrective script for mis-stamped captured_at based on filename epoch. |
| backend/pyproject.toml | Adds uv cooloff config (exclude-newer) documentation and setting. |
| backend/.dockerignore | Excludes local venv/ directories from Docker build contexts. |
Review details
- Files reviewed: 63/66 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+10
to
+11
| /** Colours of the pills and their leaders. The default is the on-screen | ||
| * look (white on translucent black); a host printing on paper flips it. */ |
Comment on lines
+402
to
+404
| modifier = Modifier | ||
| .clickable(onClick = onCloseDebug) | ||
| .padding(horizontal = 6.dp, vertical = 2.dp) |
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.
No description provided.