cdgit

cdgit pushed to main at code/room_cal

  • c401196404 diff: don't report boundary-touching checkouts as added reservations _diff_reservations excluded local calendar events whose checkin predates the window ("belongs to a prior period's scope") but applied no matching filter to the service-side reservations, which touch the same boundary differently: Booking.com's get_reservations returns any stay whose checkout lands exactly on start_date, even though it has zero actual nights in the window. That asymmetry showed up as a spurious 'added' reservation diff on every month boundary with a checkout on the 1st. Apply the same start_date >= window-start rule to the service side so both sides attribute a reservation to the same period.

5 days ago

cdgit pushed to main at code/room_cal

  • 06d093d294 har_audit: check booking header drift across all captured calls _audit_booking compared only found[key][0], so a single stale/transient header value in one captured call (e.g. a frontend bundle reload mid-session) flagged false drift even when every other call — and our client — agreed. A header now only counts as stale if the client's value never appears in any capture of that endpoint. Confirmed against two HARs from different times that the flagged conversationMessages mismatch was exactly this: an older capture has only the stale value, a newer one is mostly the current value with one straggler, so our client was already correct.

5 days ago

cdgit pushed to main at code/room_cal

  • 823194fe1a har_audit: diff GQL variable keys, not just the persisted-query hash Updating a hash alone doesn't catch a variable schema change on the same persisted query, which is exactly how last session's drift fix missed includeCustomSettings/getWishlistData/skipOldMessagePreviewFields. Each Airbnb op in DRIFT_MANIFEST now declares var_keys: the top-level variable keys its client method currently sends. har_audit.py unions the variables keys across every captured call of that op in the HAR (the same op can show different shapes depending on which page/action triggered it) and reports any added/removed/renamed key as drift, alongside the existing hash check. var_keys is hand-maintained, same as the hash constants it sits next to — whoever updates a client method's variables is expected to update this set in the same commit.

5 days ago

cdgit pushed to main at code/room_cal

  • c1724fa80d Fix Airbnb GQL variable drift missed by last session's hash update Updating _HASHES alone isn't enough when a persisted query's variable schema changes too. Diffed the live HAR capture against each op's hardcoded variables and found three mismatches: - getDLSHostCalendar: missing includeCustomSettings, causing the ValidationError (data: null) behind the empty/priceless calendar warnings. - ViaductGetThreadAndDataQuery: skipOldMessagePreviewFields renamed to getWishlistData. - CreateBulkMessagesMutation: skipOldMessagePreviewFields dropped from this mutation's input entirely.

5 days ago

cdgit pushed to main at code/room_cal

  • 56376af978 Log GQL errors returned by Airbnb query responses getDLSHostCalendar (and other _gql-routed queries) can return data: null with an errors payload that isn't auth-shaped, which previously surfaced only as a generic empty/priceless calendar warning with no indication of why.

5 days ago

cdgit pushed to main at code/room_cal

  • 581e12aa0d Fix NoneType crash when Airbnb GQL returns data: null resp.get('data', {}) only falls back to {} when the key is missing, not when the value is None, so a non-auth-shaped GQL error (data: null) crashed the next .get() call in the chain instead of degrading gracefully.

5 days ago

cdgit pushed to main at code/room_cal

  • 8862f8d8fd remove: unmaintained MCP server room_cal/mcp/, its tests, docker wiring (compose service, entrypoint branch, Dockerfile dep group), pyproject entry point/dependency group, and README mentions. Unused since going into production. Dropping fastmcp's transitive deps surfaced a real gap: room_cal.api._auth imports jwt/cryptography directly but neither was an explicit dependency -- only present via fastmcp's dependency tree. Added pyjwt[crypto] to the api group so the api service still has what it actually needs.
  • c7ad494408 feat: authoritative per-reservation-ID status check Adds check_reservation_status to both clients (Airbnb via StayHostingDetailsQuery's isCanceled, Booking via reservationDetails' is_cancelled -- no new credential capture needed for the latter, a HAR confirmed the x-booking-as-token some other page attaches isn't endpoint-enforced) and a hasattr-dispatched ServiceAPI entry point. fetch_reservations.py's deletion guard moves from per-service to per-ID: a service returning 0 reservations with N stored no longer blanket-guards the whole service -- each missing ID is checked individually, with the existing delayed re-query as fallback for whatever the status check can't resolve. Fixes a slow-season false positive where a genuinely canceled reservation got stuck forever because a service legitimately having 0-1 bookings in a window was treated as inherently suspicious.
  • 0df95431fe fix: update stale Booking.com GraphQL client-version and timeout Drift audit (scripts/har_audit.py) against a comprehensive Booking HAR found apollographql-client-version and x-envoy-expected-rq-timeout-ms stale for updateRoomDateMutation and messaging ops. inventory/fetch and inventory/update (used elsewhere by get_calendar/set_price) no longer appear in any capture -- the live UI has moved those to GraphQL; that migration is scoped separately and not included here.
  • fea850b546 fix: handle remembered-account screen in Airbnb auto-login A device-remembered session with no live cookies shows neither the #phone-or-email input nor #password directly — the username step is skipped and the same submit button the cold flow uses is already clickable. Detect this as a fallback when the warm/cold race times out (submit button visible, #phone-or-email not), click it without filling email, and merge into the cold flow's existing post-submit path.
  • 23def3c05a fix: update stale Airbnb GQL hashes and client version Comprehensive drift audit (scripts/har_audit.py against a HAR covering login, calendar, messaging, price/availability writes, and reservation details) found 5 of 7 operation hashes and X-Client-Version stale: getDLSHostCalendar, EditPanelAvailabilityMutation, StayHostingDetailsQuery, ViaductGetThreadAndDataQuery, CreateBulkMessagesMutation.
  • View comparison for these 5 commits »

5 days ago

cdgit pushed to main at code/mnemonic_port

  • 1f3595a4cb Add JavaScript prefix predictor for the mnemonicode wordlist Refines matches as characters are typed and reports a completion once the prefix narrows to a single word, rather than assuming a fixed prefix length is always unique.

2 weeks ago

cdgit pushed to main at code/GxEPD2-esphome

  • a95be5ac65 Write the zEPD ESPHome adapter; full compile verified end-to-end display.py / zepd_display.h: the engine adapter, ported from the legacy gxepd2_display.h with CycleEngine->Engine, EpdPanelDescriptor->EngineModel, PANELS dict -> registry.load_model(). Wires codegen.generate_cpp() into to_code() as a fixed-name generated header (zepd_model_generated.h) at src/ root, per DESCRIPTOR_REGISTRY_PLAN.md's decided mechanism. Two real structural fixes found only by actually compiling: - engine.h/engine_model.h moved out of an engine/ subdirectory: ESPHome's external-component loader only auto-copies source files directly in a component's top-level package directory (ComponentManifest.resources is non-recursive by default) - a plain subdirectory's headers never reach the build at all. Flattened into components/zepd/ directly. - Engine/EngineModel moved from a separate global `zepd` namespace into esphome::zepd (the adapter's namespace), not kept apart. ESPHome's generated main.cpp references the display class as unqualified `zepd::ZepdDisplay`, relying on `using namespace esphome;` to resolve it - a second, unrelated top-level `zepd` namespace made that ambiguous at compile time. One namespace, no collision. Added tests/test_smoke_zepd.esp32-arduino.yaml (GxEPD2_102, the one registry-backed model) and confirmed `esphome compile` succeeds: config validates, codegen runs, C++ compiles and links to a working firmware image. Both fixes are corrections to claude/ZEPD_PLAN.md's assumptions, not yet reflected there - plan update is the next step.
  • a179e39d8d Add zEPD JSON model registry: loader, codegen, default.json schema registry.py: pure-Python drivers/*.json loader implementing the 3-tier recursive merge (default.json -> named model -> optional YAML overlay) from claude/DESCRIPTOR_REGISTRY_PLAN.md, with unknown-key warnings. codegen.py: pure (dict) -> C++ str emitter producing an inline const zepd::EngineModel plus its backing EpdCommand/palette arrays - no ESPHome/cg/CORE dependency, so it's callable from both display.py's to_code() and standalone host tooling. default.json: the full schema, generated programmatically from the legacy panel_102.h (GxEPD2_102, a small mono panel) rather than hand-transcribed, to rule out byte-transcription error. Verified byte-for-byte identical to the legacy C++ array data via codegen round-trip before committing.
  • 5fd8bcca0d Copy engine core forward as zepd::Engine/EngineModel Renamed from the legacy components/gxepd2 CycleEngine/EpdPanelDescriptor: namespace esphome::gxepd2 -> zepd (no project-name stutter inside its own namespace), CycleEngine -> Engine, EpdPanelDescriptor -> EngineModel, GXEPD2_PALETTE_* -> PALETTE_*, log tag/diagnostics define gxepd2 -> zepd. Still directly uses ESPHome GPIO/SPI/log calls - full adapter decoupling is future work, not this pass (see claude/ZEPD_PLAN.md). No behavior change: logic is otherwise untouched from cycle_engine.h / epd_command.h.
  • ffee2a79af Scaffold esphome-zepd and esphome-docs directories Empty scaffold for the zEPD rebuild per claude/ZEPD_PLAN.md. Legacy components/gxepd2 is left untouched as the before/after baseline.
  • View comparison for these 4 commits »

2 weeks ago

cdgit pushed to main at code/GxEPD2-esphome

  • 09e2910950 Abort mid-refresh with a reset pulse before power_off()/hibernate() Most controllers ignore new commands outright while BUSY is asserted, so writing power_off_sequence/hibernate_sequence over a still-running refresh silently did nothing. When a reset pin is wired, force the pulse first to abort the cycle and bring the controller back to a command-accepting state; without one, fall back to the previous best-effort behavior.

2 weeks ago

cdgit pushed to main at code/GxEPD2-esphome

  • 7bd500fbca Hook deep_sleep/safe_reboot via Component::on_powerdown(), add idle() ESPHome calls on_powerdown() on every component right before deep_sleep suspends the MCU (and on safe_reboot()) - confirmed in the vendored esphome core (Application::run_powerdown_hooks(), deep_sleep's should_teardown_() unconditional on every supported platform). Override it to call hibernate() when a reset pin is configured, else power_off() - no YAML/lambda needed for the common case, no engine change, since hibernate()'s "no paired wake call needed" property already made this free. Deliberately skips a Component::teardown() override to wait out a mid-flight refresh: ESPHome's teardown budget is fixed by the caller and short (1s reboot, 5s deep sleep) - sized for a network flush, not a 45-60s panel refresh - so stalling teardown would always lose that race and get force-advanced anyway. Exposed GxEPD2Display::idle() publicly instead, so a caller who cares about the race (or a more complex application tracking its own update sequence) can check it and decide for themselves. Compiled clean against both esp32-arduino and esp32-idf test configs; check_descriptors.py unaffected (61/61).
  • 7ff01ebccf Drop 213c inter-panel notes; flip 730c to verified 213c_gdey0213f51/f52: each panel is an independent driver from a caller's perspective, so cross-references to a sibling panel's command template or hardware-identity ambiguity don't belong in the panel-specific header. GxEPD2_730c_GDEP073E01 is confirmed working - flip status to verified in display.py/CONTRIBUTING.md and drop the stale "structural, not yet bench-verified" comment from the panel file rather than replacing it with verification narrative (status flag is sufficient; git history has the rest).
  • cd1730f91c Dedupe generalized engine facts out of generated panel headers The vendor "always-reset-every-cycle means a driver's own _hibernating || _initial_write reset-gating needs no accommodation" fact was restated nearly verbatim in three separate panel_*.h files. It's a property of CycleEngine::request_full_update(), true for every panel - moved to one spot in CONTRIBUTING.md's Execution engine section instead. panel_579c_gdey0579f51.h's "history worth not repeating" narrative duplicated the gxepd2_579_retest_plan project memory almost exactly; trimmed to a pointer, keeping only the facts a maintainer of this descriptor still needs (PAIRED_EDGES rationale, region mapping). Dropped panel_579c_gdey0579f51_master_only.h entirely: a diagnostic-only variant from before the real dual-region descriptor worked, whose header was stale speculation (wrong PSR values, mirroring theories) already contradicted by the resolved real panel. Removed its display.py/gxepd2_display.h wiring and the now-dead MASTER_ONLY example in check_descriptors.py/README.md. check_descriptors.py: 61 reproduced, 0 known-diff, 0 skipped (was 1 skipped).
  • 187fa75490 Capture writeImageForFullRefresh's extra RAM-bank write for partial refresh GxEPD2's writeImageForFullRefresh() diverges from plain writeImage() on SSD1680/SSD1681/SSD1619-family controllers: it writes the same bitmap to a "previous RAM" bank before the normal "current RAM" write, so a later partial update's internal diff starts from a correct baseline. This engine is full-refresh-only today (that waveform doesn't read the previous bank), so it costs nothing now, but it's exactly the state partial refresh would need - captured now via a generic emit_descriptor probe rather than needing a second capture pass later. New EpdPanelDescriptor::plane_write_cmd_for_full_refresh field (default {0,0}, not read by CycleEngine yet) plus a capture.py --full-refresh splice mode. Swept all 61 shipped mono/3-color panels: 27 genuinely diverge: 25 plain auto-generated descriptors regenerated wholesale, 2 with hand-authored documentation (panel_426_gdeq0426t82.h, panel_583_gdeq0583t31.h) spliced by hand to preserve it. check_descriptors.py reports all 61 panels unchanged.
  • View comparison for these 4 commits »

2 weeks ago

cdgit pushed to main at code/GxEPD2-esphome

  • af3e1cf753 docs: broaden hibernate() hardware confirmation to all 7 verified panels Same WAIT_BUSY-timeout / reset-wake result held across every controller family on hand (SSD16xx, UC8179, JD79676, HX8717), not just GDEQ0426T82 - this is GxEPD2's general hibernate() contract, not a one-chip quirk.

2 weeks ago

cdgit pushed to main at code/GxEPD2-esphome

  • 13bf1f3f10 check_descriptors.py: allow checking specific models, not just all Common case while iterating on one driver is checking just that panel; full registry scan stays the default so cross-project drift checks (e.g. after a vendor bump) are unchanged.

2 weeks ago

cdgit pushed to main at code/GxEPD2-esphome

  • c017690e4f Add GxEPD2_579_GDEY0579T93 and GxEPD2_750_T7 mono panel descriptors check_descriptors.py's regression pass now legitimately exceeds the autonomous queue runner's 60s subprocess timeout at this registry size (61 panels, each re-emitted from source), which caused both of these to be flagged for review mid-run with their descriptor header file left uncommitted (registry/include lines landed in the preceding commit, the header did not). Both re-validated here with no time limit: check_descriptors MATCH, esphome compile succeeds, no unresolved TODO fields. Structural, not bench-verified.
  • 2532dc15ba Add GxEPD2_750 (750) mono panel descriptor Autonomous capture-pipeline run; structural, not bench-verified.
  • a365299e23 Add GxEPD2_583_T8 (583_t8) mono panel descriptor Autonomous capture-pipeline run; structural, not bench-verified.
  • 8e55721f6c Add GxEPD2_576_GDEH0576T81_BW_SHM (576_gdeh0576t81_bw_shm) mono panel descriptor Autonomous capture-pipeline run; structural, not bench-verified.
  • 3e4280fe75 Add GxEPD2_576_GDEH0576T81_BW (576_gdeh0576t81_bw) mono panel descriptor Autonomous capture-pipeline run; structural, not bench-verified.
  • View comparison for these 45 commits »

2 weeks ago

cdgit pushed to main at code/GxEPD2-esphome

  • ee645c8a22 docs: record hardware-confirmed power_off/hibernate behavior Bench-tested on GDEQ0426T82/SSD1677 via the diag_update_no_reset() path: hibernate() genuinely stops the panel acknowledging BUSY (WAIT_BUSY times out, engine aborts), where power_off() does not - the panel responds fine with no reset needed. A normal update() after hibernate() recovers correctly, confirming RST-wake. Matches the predicted behavior exactly.

2 weeks ago

cdgit pushed to main at code/GxEPD2-esphome

  • d4f301c61b Capture power_off/hibernate for all panels; add dev-time diagnostics gate - epd_command.h: power_off_sequence/hibernate_sequence fields on EpdPanelDescriptor (both optional, default nullptr/0 - a safe no-op). - emit_descriptor.h: capture_power_sequence/capture_power_off_and_hibernate (each needs its own fresh driver session - every GxEPD2 driver's hibernate() calls powerOff() internally, so one session would silently drop the second call's commands) and print_power_sequences for splicing into an existing panel_*.h. tools/capture.py --power exposes it. - Captured and added to all 15 panels. Three SSD16xx-family panels (154_Z90c, 213_Z98C, 426_GDEQ0426T82) have an empty power_off_sequence - real, not a bug: their _Update_Full() already sets _power_is_on = false, so powerOff()'s own guard has nothing left to send. - cycle_engine.h: POWER_OFF_CMD/HIBERNATE_CMD states reusing the existing step_sequence_ command-table walker; request_power_off()/ request_hibernate() are standalone entry points (no reset pulse - no paired "wake" call exists or is needed, since request_full_update()'s RESET_LOW is an unconditional reset every cycle anyway). - gxepd2_display.h: power_off()/hibernate() exposed for a user's own lambda. - New `diagnostics: true` display option (display.py) compiles in GxEPD2Display's dev-time-only diagnostic methods (currently diag_update_no_reset(), for bench-checking hibernate()'s "non-responsive until reset" contract using the screen as the oracle) behind -DUSE_GXEPD2_DIAGNOSTICS, so a production build pays nothing for them. Off by default. - 5 new mono panels (213_B72/270/290_T5D/420/583) covering 5 previously unrepresented controller families, all structural/compile-verified. - emit_descriptor.h bug fix: the plane-invert/1bpp probe drove its test fill through writeScreenBuffer, assuming it shares a command with the real plane-write (writeImage) - GxEPD2_270 doesn't (0x13 vs 0x15), so the probe silently misclassified a genuinely 1bpp panel as packed. Fixed to probe through writeImage instead. - emit_descriptor.h: make_resolver_bw_packed, a fallback packed-palette resolver for a driver with no colour wrapper to drive (unlike the 4-colour/7-colour resolvers) - covers GxEPD2_583/UC8159c's _send8pixel-style internal packing with no source reading needed. - tools/capture.py --sweep/--diag: run the window-encoder sweep / Pattern-A diagnostic fill generically for any capturable model, no hand-written run_<panel>[_diag].cpp needed. CONTRIBUTING.md and the harness README updated throughout.

2 weeks ago

cdgit pushed to main at code/GxEPD2-esphome

  • 996c71278c Add 5 new mono panels; generalize packed-mono resolution in the emitter GxEPD2_213_B72 (SSD1675A), GxEPD2_270 (EK79652), GxEPD2_290_T5D (UC8151D), GxEPD2_420 (UC8176), GxEPD2_583 (UC8159c) - five controller families with no prior coverage, captured and wired in as "structural". All compile clean on esp32-idf; check_descriptors.py still matches every prior panel. Along the way: - tools/capture.py: add --sweep/--diag, so a new panel's window-encoder sweep and Pattern-A diag fill run generically (run_harness.h / run_diag_harness.h are already templates over Driver) with no hand-written run_<panel>[_diag].cpp needed. - emit_descriptor.h: fix the plane-invert/1bpp probe, which drove its test fill through writeScreenBuffer on the assumption that it shares a command with the real plane-write (writeImage). GxEPD2_270 doesn't (clear=0x13, image-plane=0x15), so the probe silently misclassified a genuinely 1bpp panel as packed. Probe through writeImage instead, the same call that produced the plane-write command in the first place. - emit_descriptor.h: add make_resolver_bw_packed, a fallback packed resolver for a driver with no colour wrapper to drive (unlike the 4-colour/7-colour resolvers, which observe a GxEPD2_4C/7C wrapper's named-colour fills). GxEPD2_583/UC8159c is wrapped GxEPD2_BW (binary) but its _send8pixel expands each 1bpp buffer byte into a wider wire format using only two codes; the new resolver derives bits_per_pixel and both codes by driving plain writeImage with uniform white/black 1bpp fills and checking the wire burst is internally uniform - no source reading needed, and capture.py now emits a complete descriptor (palette array included) for this shape. CONTRIBUTING.md and the harness README updated for both.

2 weeks ago

cdgit pushed to main at code/GxEPD2-esphome

  • d647f286b6 Add GDEP073E01 (7.3in Spectra6 800x480) panel descriptor Captured via tools/capture.py from the vendored GxEPD2_730c_GDEP073E01 driver, structurally matched against a fresh emit (check_descriptors.py) and the standard window-encoder sweep. First panel on the engine's 4bpp/7-color draw_packed_ path. Two surprises this panel's descriptor hand-fills beyond the emitter's default output, both backed by driving the real driver rather than reading its source by hand: - Non-standard 7-color code map (driver remaps the wire code after GxEPD2_7C's standard palette via _convert_to_native) - captured as its own PANEL_730C_GDEP073E01_PALETTE, not GXEPD2_PALETTE_7C. - Asymmetric window-end encoding on 0x83: X end is inclusive (x+w-1), Y end is exclusive (y+h) - confirmed across a 5-point window sweep, not just the full-frame capture. Status: structural only, not yet bench-verified.

3 weeks ago

cdgit pushed to main at code/GxEPD2-esphome

  • f6874087be emitter: auto-fill 7-colour fields via emit_palette_7c.h Same approach as the 4-colour resolver: drive the vendor driver's own GxEPD2_7C wrapper, read each named colour's wire code, and validate against the engine's GXEPD2_PALETTE_7C before filling bits_per_pixel/palette/blank_code. A non-standard 7-colour map (e.g. GDEP073E01's rotated codes) stays an explicit TODO with the observed table instead of being silently mislabelled. Also fixes the generated comment mislabelling a 4bpp panel as "16-colour packed" (2^bpp) instead of its actual probed colour count.

3 weeks ago

cdgit pushed to main at code/GxEPD2-esphome

  • 924158f1dc emitter: segment dual-region panels (GDEY0579F51 reproduces end to end) The descriptor emitter now models the region-select layer of a dual-chip master/slave panel, so GxEPD2_579c_GDEY0579F51 is reproduced from its driver instead of hand-assembled. Four additions on the existing interleaved multi-plane path, all observation-based: - INIT recovery. A dual-region panel runs its whole _InitDisplay during the writeScreenBuffer clear, and that init reuses the region-select opcode (0xA2 select per region for PSR), so the single-plane "cut at first window opcode" rule fails. Instead take init from the clear stage and drop only the contiguous trailing run of window-set commands before the first clear burst. - plane_width, decoded from the region window-set payload (x_end-x+1 = 396). - line_order. A region whose burst is twice its pixel bytes is sending two wire lines per row (GxEPD2 writeNative's doubled emission) -> candidate PAIRED_EDGES. The doubling is observable; the exact edges-to-middle row order is not (a uniform fill can't show which buffer row each line carries), so it's emitted as a candidate for the diag native-path pass / driver source to confirm. - refresh keeps its distinct both-region window (dropped for a shared-region panel as a duplicate), and trims the window GxEPD2 re-sets after the trigger to prime its next writeImage (dead in our per-cycle replay). The emitted 579 is structurally identical to the committed hand-derived header (INIT/WINDOW/REFRESH, plane layout, plane_width, line_order, blank_code all match; only the dev-tunable busy_timeout differs). Promoted out of check_descriptors' KNOWN_DIFF -> all 9 panels reproduce, 0 known-diff. The shipped panel_579c header is unchanged (keeps its tuned timeout and history). What still needs hand work: a panel with more than two regions, or asymmetric regions, and the exact PAIRED_EDGES row order confirmation.
  • 65918c7bdb emitter: auto-fill 4-colour fields, class-aware busy timeout, paste-ready PANELS line Three steps toward a mechanical single-region capture->codegen: - 4-colour packed panels now come out of capture.py complete. emit_palette_4c.h folds the diag Pattern-B colour->code derivation into the capture: it drives the driver's own GxEPD2_4C wrapper, reads each named colour's wire code, validates the table against the engine's GXEPD2_PALETTE_4C, and fills bits_per_pixel/palette/blank_code. A non-standard map stays unresolved and emits the old TODO plus the observed table, so a surprise is never silently mislabelled. emit_descriptor gained a PackedResolver seam; the generic path (mono/3-colour/7-colour) is unchanged. - busy_timeout_ms is now a generous, class-aware default instead of a flat full_refresh_time+5000: max(2x vendor estimate, per-depth floor) where the floor is 15s (1bpp), 45s (2bpp 4-colour), 60s (4bpp 7-colour). It's only a diagnostic ceiling, so erring long is free; the emitted comment says it's dev-tunable. Shipped bench-verified panels keep their tuned values. - capture.py prints the exact PANELS line to paste into display.py. Key, descriptor macro and format are derived from the model name (correct by construction, can't be mistyped); part/controller are scraped from the driver header to sanity-check; status starts at "structural". check_descriptors stays green (8 reproduced, 579 known-diff).
  • View comparison for these 2 commits »

3 weeks ago