{"id":"6f5e27ab-96c9-4c64-8752-add9e1f7e28d","caseId":"django-714266734","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"Change correctly replaces dict-ordering workaround but hard-coding `sort_keys=False` as a keyword before `**self.options` makes any caller-supplied `sort_keys` crash with `TypeError: multiple values` instead of being honoured/overridden; use `setdefault` like `allow_unicode`.","findings":[{"file":"django/core/serializers/pyyaml.py","line":59,"severity":"blocker","title":"Duplicate keyword `sort_keys` raises TypeError when caller passes `sort_keys`","explanation":"end_serialization() does `yaml.dump(..., sort_keys=False, **self.options)`. Python raises `TypeError: got multiple values for keyword argument 'sort_keys'` if `self.options` (populated from `Serializer.serialize(..., **options)`) contains `sort_keys`. Previously the call had no explicit `sort_keys` kwarg so a user could pass `serializers.serialize('yaml', qs, sort_keys=True/False)` without crashing (the custom dict representer simply ignored it). It also prevents legitimate override to `True` for sorted output. Fix by using `self.options.setdefault(\"sort_keys\", False)` and then `yaml.dump(..., Dumper=DjangoSafeDumper, **self.options)` consistent with `allow_unicode` handling."},{"file":"django/core/serializers/pyyaml.py","line":63,"severity":"minor","title":"Implicit minimum PyYAML bump to ≥5.1 not documented","explanation":"Removing the `add_representer(dict, ...)` workaround relies on `sort_keys` added in PyYAML 5.1. On PyYAML <5.1 `yaml.dump(sort_keys=False)` raises `TypeError: dump() got an unexpected keyword argument 'sort_keys'`. Current `tests/requirements`/`docs/releases/6.0.txt` require PyYAML ≥6.0.2 so main is safe, but the change should ensure release notes or a guard notes the dropped support for older PyYAML versions."}]},"text":"Reviewing the YAML order-preservation switch — checking for regressions in dict handling.I've identified a key compatibility issue with this change — now I'll finalize the review.{\"verdict\":\"request_changes\",\"summary\":\"Change correctly replaces dict-ordering workaround but hard-coding `sort_keys=False` as a keyword before `**self.options` makes any caller-supplied `sort_keys` crash with `TypeError: multiple values` instead of being honoured/overridden; use `setdefault` like `allow_unicode`.\",\"findings\":[{\"file\":\"django/core/serializers/pyyaml.py\",\"line\":59,\"severity\":\"blocker\",\"title\":\"Duplicate keyword `sort_keys` raises TypeError when caller passes `sort_keys`\",\"explanation\":\"end_serialization() does `yaml.dump(..., sort_keys=False, **self.options)`. Python raises `TypeError: got multiple values for keyword argument 'sort_keys'` if `self.options` (populated from `Serializer.serialize(..., **options)`) contains `sort_keys`. Previously the call had no explicit `sort_keys` kwarg so a user could pass `serializers.serialize('yaml', qs, sort_keys=True/False)` without crashing (the custom dict representer simply ignored it). It also prevents legitimate override to `True` for sorted output. Fix by using `self.options.setdefault(\\\"sort_keys\\\", False)` and then `yaml.dump(..., Dumper=DjangoSafeDumper, **self.options)` consistent with `allow_unicode` handling.\"},{\"file\":\"django/core/serializers/pyyaml.py\",\"line\":63,\"severity\":\"minor\",\"title\":\"Implicit minimum PyYAML bump to ≥5.1 not documented\",\"explanation\":\"Removing the `add_representer(dict, ...)` workaround relies on `sort_keys` added in PyYAML 5.1. On PyYAML <5.1 `yaml.dump(sort_keys=False)` raises `TypeError: dump() got an unexpected keyword argument 'sort_keys'`. Current `tests/requirements`/`docs/releases/6.0.txt` require PyYAML ≥6.0.2 so main is safe, but the change should ensure release notes or a guard notes the dropped support for older PyYAML versions.\"}]}","startedAt":"2026-10-16T07:46:55.773Z","finishedAt":"2026-10-16T07:47:34.881Z"}
{"id":"90929bd7-982d-4d92-8847-216e317f1573","caseId":"django-7b54ddd5e","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Change correctly wraps all direct values in Value(output_field) when an expression is present, fixing JSONField __in adaptation via get_db_prep_value/get_db_prep_save, and adds targeted tests.","findings":[{"file":"django/db/models/lookups.py","line":290,"severity":"minor","title":"None values now wrapped and not stripped in mixed In","explanation":"When rhs contains an expression, direct Python None is now wrapped as Value(None, lhs.output_field) instead of staying as raw None. In.process_rhs discards raw None from IN lists (via OrderedSet.discard(None) / list comp) but will not discard a Value wrapper, so IN (expr, NULL) may be generated instead of IN (expr). Previously only strings were wrapped so raw None was still stripped. Behavioural difference is low-impact (NULL in IN never matches) but diverges from prior optimization and EmptyResultSet handling."}]},"text":"Reviewing the lookup wrapping change for edge-case regressions — inspecting the surrounding logic.I've mapped how the new value wrapping works — now checking tricky scenarios that might break the lookup.{\"verdict\":\"approve\",\"summary\":\"Change correctly wraps all direct values in Value(output_field) when an expression is present, fixing JSONField __in adaptation via get_db_prep_value/get_db_prep_save, and adds targeted tests.\",\"findings\":[{\"file\":\"django/db/models/lookups.py\",\"line\":290,\"severity\":\"minor\",\"title\":\"None values now wrapped and not stripped in mixed In\",\"explanation\":\"When rhs contains an expression, direct Python None is now wrapped as Value(None, lhs.output_field) instead of staying as raw None. In.process_rhs discards raw None from IN lists (via OrderedSet.discard(None) / list comp) but will not discard a Value wrapper, so IN (expr, NULL) may be generated instead of IN (expr). Previously only strings were wrapped so raw None was still stripped. Behavioural difference is low-impact (NULL in IN never matches) but diverges from prior optimization and EmptyResultSet handling.\"}]}","startedAt":"2026-10-16T07:47:34.884Z","finishedAt":"2026-10-16T07:48:59.489Z"}
{"id":"1c11ab2e-84d0-4270-b9ee-0b6c0269e574","caseId":"django-821619aa8","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"Breaking signature change (`**hints` -> `*, instance`) and dropped `instance` hint handling for custom prefetches breaks backward compatibility and multi-db routing.","findings":[{"file":"django/db/models/fields/related_descriptors.py","line":169,"severity":"major","title":"Breaking public API: get_queryset() now requires keyword-only `instance`","explanation":"Old code was `def get_queryset(self, **hints)` so `desc.get_queryset()` or `desc.get_queryset(instance=obj)` and `desc.get_queryset(db='other')` worked and arbitrary hints were forwarded to `db_manager(hints=hints)`. New code `def get_queryset(self, *, instance)` makes any call without `instance=` raise TypeError, and any existing subclass overriding `def get_queryset(self, **hints)` that is instantiated via the framework will now be called as `get_queryset(instance=inst)` – which still collects in `**hints` but internal callers that expect to pass through extra hints lose them (hardcoded `hints={\"instance\": instance}`). This breaks external descriptors (e.g. custom ForeignObject subclasses in tests/foreign_object) and any third-party code inspecting/calling the descriptor directly. Should retain backwards-compat shim e.g. `def get_queryset(self, instance=None, **hints)` merging hints."},{"file":"django/db/models/fields/related_descriptors.py","line":174,"severity":"major","title":"Missing instance hint when `querysets` supplied in get_prefetch_querysets()","explanation":"Previously: `queryset = querysets[0] if querysets else self.get_queryset(); queryset._add_hints(instance=instances[0])` – hint was added regardless of source. Now: `queryset = querysets[0] if querysets else self.get_queryset(instance=instances[0])` with no following `_add_hints`. When a user supplies `Prefetch('field', queryset=custom_qs)` the queryset loses the `instance` hint needed for `router.db_for_read(..., instance=...)`. Other managers (ReverseManyToOneDescriptor, ManyToMany) still do `queryset._add_hints(...)` after the ternary. Fix: add branch that calls `_add_hints` for the custom-queryset case."},{"file":"django/db/models/fields/related_descriptors.py","line":462,"severity":"major","title":"Same breaking signature and hint loss on ReverseOneToOneDescriptor","explanation":"Identical issue as ForwardManyToOneDescriptor at line 169/174 applies to ReverseOneToOneDescriptor.get_queryset()/get_prefetch_querysets() at 462/467: API break and omitted `_add_hints` for provided querysets."}]},"text":"Reviewing this descriptor change — checking callers for breaking signature issues.I spotted a potential compatibility gap — digging deeper into edge cases and expected behavior.{\"verdict\":\"request_changes\",\"summary\":\"Breaking signature change (`**hints` -> `*, instance`) and dropped `instance` hint handling for custom prefetches breaks backward compatibility and multi-db routing.\",\"findings\":[{\"file\":\"django/db/models/fields/related_descriptors.py\",\"line\":169,\"severity\":\"major\",\"title\":\"Breaking public API: get_queryset() now requires keyword-only `instance`\",\"explanation\":\"Old code was `def get_queryset(self, **hints)` so `desc.get_queryset()` or `desc.get_queryset(instance=obj)` and `desc.get_queryset(db='other')` worked and arbitrary hints were forwarded to `db_manager(hints=hints)`. New code `def get_queryset(self, *, instance)` makes any call without `instance=` raise TypeError, and any existing subclass overriding `def get_queryset(self, **hints)` that is instantiated via the framework will now be called as `get_queryset(instance=inst)` – which still collects in `**hints` but internal callers that expect to pass through extra hints lose them (hardcoded `hints={\\\"instance\\\": instance}`). This breaks external descriptors (e.g. custom ForeignObject subclasses in tests/foreign_object) and any third-party code inspecting/calling the descriptor directly. Should retain backwards-compat shim e.g. `def get_queryset(self, instance=None, **hints)` merging hints.\"},{\"file\":\"django/db/models/fields/related_descriptors.py\",\"line\":174,\"severity\":\"major\",\"title\":\"Missing instance hint when `querysets` supplied in get_prefetch_querysets()\",\"explanation\":\"Previously: `queryset = querysets[0] if querysets else self.get_queryset(); queryset._add_hints(instance=instances[0])` – hint was added regardless of source. Now: `queryset = querysets[0] if querysets else self.get_queryset(instance=instances[0])` with no following `_add_hints`. When a user supplies `Prefetch('field', queryset=custom_qs)` the queryset loses the `instance` hint needed for `router.db_for_read(..., instance=...)`. Other managers (ReverseManyToOneDescriptor, ManyToMany) still do `queryset._add_hints(...)` after the ternary. Fix: add branch that calls `_add_hints` for the custom-queryset case.\"},{\"file\":\"django/db/models/fields/related_descriptors.py\",\"line\":462,\"severity\":\"major\",\"title\":\"Same breaking signature and hint loss on ReverseOneToOneDescriptor\",\"explanation\":\"Identical issue as ForwardManyToOneDescriptor at line 169/174 applies to ReverseOneToOneDescriptor.get_queryset()/get_prefetch_querysets() at 462/467: API break and omitted `_add_hints` for provided querysets.\"}]}","startedAt":"2026-10-16T07:48:59.494Z","finishedAt":"2026-10-16T07:50:17.706Z"}
{"id":"5b9b3556-ca14-406a-85da-d51df1b535e5","caseId":"django-8096b5251","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Change correctly introduces Stylesheet analogous to Script, reuses MediaAsset rendering and preserves ordering/deduplication/media handling. One minor compatibility edge remains (rel= handling).","findings":[{"file":"django/forms/widgets.py","line":127,"severity":"minor","title":"TypeError on explicit rel_kwarg","explanation":"Stylesheet.__init__ does super().__init__(path=href, rel=\"stylesheet\", **attributes). If caller passes rel explicitly (e.g. Stylesheet(\"a.css\", rel=\"alternate\")) Python raises TypeError: multiple values for keyword argument 'rel', whereas the previous custom CSS test helper silently overwrote to stylesheet. Should pop/validate rel from attributes before forwarding to preserve previous graceful overwriting or raise a clear ValueError."}]},"text":"Inspecting the stylesheet media change for rendering and equality regressions.I’ve mapped the core rendering flow; now I’ll scan the codebase for related asset handling.{\"verdict\":\"approve\",\"summary\":\"Change correctly introduces Stylesheet analogous to Script, reuses MediaAsset rendering and preserves ordering/deduplication/media handling. One minor compatibility edge remains (rel= handling).\",\"findings\":[{\"file\":\"django/forms/widgets.py\",\"line\":127,\"severity\":\"minor\",\"title\":\"TypeError on explicit rel_kwarg\",\"explanation\":\"Stylesheet.__init__ does super().__init__(path=href, rel=\\\"stylesheet\\\", **attributes). If caller passes rel explicitly (e.g. Stylesheet(\\\"a.css\\\", rel=\\\"alternate\\\")) Python raises TypeError: multiple values for keyword argument 'rel', whereas the previous custom CSS test helper silently overwrote to stylesheet. Should pop/validate rel from attributes before forwarding to preserve previous graceful overwriting or raise a clear ValueError.\"}]}","startedAt":"2026-10-16T07:50:17.710Z","finishedAt":"2026-10-16T07:51:16.363Z"}
{"id":"42e8cb64-75d3-43c5-898f-b3ef244f1b88","caseId":"django-a2348c85f","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Change makes `_is_pk_set()` treat `DatabaseDefault` sentinel the same as `None` (including per-element for composite PK tuples), fixing inline formset crash where an unsaved parent with `db_default` on its PK was incorrectly considered saved. Covers single and composite PK cases, matches new tests.","findings":[]},"text":"Patch tightens PK emptiness check for DB-default keys — I'll verify the fix has no side effects.I've traced how DB-defaults flow into the formset crash — now I'll examine existing checks and edge cases.{\"verdict\":\"approve\",\"summary\":\"Change makes `_is_pk_set()` treat `DatabaseDefault` sentinel the same as `None` (including per-element for composite PK tuples), fixing inline formset crash where an unsaved parent with `db_default` on its PK was incorrectly considered saved. Covers single and composite PK cases, matches new tests.\",\"findings\":[]}","startedAt":"2026-10-16T07:51:16.367Z","finishedAt":"2026-10-16T07:51:56.650Z"}
{"id":"ccc33b6f-3939-48fd-a20b-9c8149a97f10","caseId":"django-7d9aab8da","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"The refactor avoids a second loop but drops the `not isinstance(pk, DatabaseDefault)` guard after `get_pk_value_on_save()`, so a PK that becomes a `DatabaseDefault` via its default is now treated as with-pk instead of without-pk.","findings":[{"file":"django/db/models/query.py","line":680,"severity":"major","title":"Missing DatabaseDefault check after PK generation changes bulk_create split","explanation":"Old partition was `o._is_pk_set() and not isinstance(o.pk, DatabaseDefault)`. New else-branch does `obj.pk = get_pk_value_on_save(); if obj._is_pk_set(): with_pk else without_pk`, omitting the `not isinstance(..., DatabaseDefault)` test. If `get_pk_value_on_save()` returns a `DatabaseDefault` (e.g. `primary_key` with `default=DatabaseDefault(...)`), old code put it in `objs_without_pk` to let the DB supply the value; new code incorrectly puts it in `objs_with_pk`, changing the INSERT columns/mode and breaking parity with previous behaviour."}]},"text":"Moving logic into a single pass risks subtle split differences — I'll verify the new partitioning against the original behavior.I've mapped out how primary keys are normally split; now I'll dig into edge cases around system-provided defaults.{\"verdict\":\"request_changes\",\"summary\":\"The refactor avoids a second loop but drops the `not isinstance(pk, DatabaseDefault)` guard after `get_pk_value_on_save()`, so a PK that becomes a `DatabaseDefault` via its default is now treated as with-pk instead of without-pk.\",\"findings\":[{\"file\":\"django/db/models/query.py\",\"line\":680,\"severity\":\"major\",\"title\":\"Missing DatabaseDefault check after PK generation changes bulk_create split\",\"explanation\":\"Old partition was `o._is_pk_set() and not isinstance(o.pk, DatabaseDefault)`. New else-branch does `obj.pk = get_pk_value_on_save(); if obj._is_pk_set(): with_pk else without_pk`, omitting the `not isinstance(..., DatabaseDefault)` test. If `get_pk_value_on_save()` returns a `DatabaseDefault` (e.g. `primary_key` with `default=DatabaseDefault(...)`), old code put it in `objs_without_pk` to let the DB supply the value; new code incorrectly puts it in `objs_with_pk`, changing the INSERT columns/mode and breaking parity with previous behaviour.\"}]}","startedAt":"2026-10-16T07:51:56.655Z","finishedAt":"2026-10-16T07:53:04.824Z"}
{"id":"b6dc9977-d3b8-4939-9740-9484e8451dcc","caseId":"django-78fac1b04","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"PR adds bytes support but PBKDF2/MD5 incorrectly use force_str for password limiting bytes to valid UTF-8 and BasePasswordHasher salt check can raise DjangoUnicodeDecodeError on arbitrary binary salt; Scrypt/Argon2/BCrypt correctly use force_bytes causing inconsistent API.","findings":[{"file":"django/contrib/auth/hashers.py","line":256,"severity":"major","title":"_check_encode_args uses force_str for bytes salt and can raise decode error","explanation":"if salt=b\"\\xff\\xfe\" or any non-utf8 bytes, force_str(salt) raises DjangoUnicodeDecodeError instead of checking for \"$\" and raising ValueError. Previous code raised TypeError for bytes, but intent is to accept bytes. Use force_bytes(salt) and check b\"$\" in ... or handle both str/bytes without decoding. Reproducer: BasePasswordHasher()._check_encode_args(\"pwd\", b\"\\xff\")"},{"file":"django/contrib/auth/hashers.py","line":328,"severity":"major","title":"PBKDF2PasswordHasher.encode uses force_str(password) rejecting binary passwords","explanation":"password=force_str(password) fails for arbitrary bytes (e.g. b\"\\x80\\xff\")). pbkdf2() itself uses force_bytes and supports any bytes, as do Argon2/BCrypt/Scrypt which use force_bytes. Inconsistent: MD5/PBKDF2 reject binary passwords while others accept. Reproducer: PBKDF2PasswordHasher().encode(b\"\\x80password\", \"seasalt\") raises DjangoUnicodeDecodeError, but Argon2PasswordHasher().encode(b\"\\x80password\",\"seasalt\") succeeds."},{"file":"django/contrib/auth/hashers.py","line":662,"severity":"major","title":"MD5PasswordHasher.encode same force_str restriction","explanation":"Same issue as PBKDF2: force_str(password)/force_str(salt) restricts to UTF-8 bytes. Binary secrets used as passwords (common for tokens) will crash. Should preserve bytes via force_bytes for hashing and only force_str salt for storage, or use bytes concatenation directly."},{"file":"django/contrib/auth/hashers.py","line":601,"severity":"minor","title":"Scrypt salt storage via force_str inherits same decode limitation","explanation":"hash uses force_bytes(salt) (good) but return uses force_str(salt) for encoded form; non-utf8 bytes salt will raise DjangoUnicodeDecodeError while hash part would have succeeded. Low risk as salts are ascii, but inconsistent with encode contract."}]},"text":"Checking the hasher changes for regressions with bytes handling.I've got the overall changes; now I'll drill into the individual hashers for subtle issues.{\"verdict\":\"request_changes\",\"summary\":\"PR adds bytes support but PBKDF2/MD5 incorrectly use force_str for password limiting bytes to valid UTF-8 and BasePasswordHasher salt check can raise DjangoUnicodeDecodeError on arbitrary binary salt; Scrypt/Argon2/BCrypt correctly use force_bytes causing inconsistent API.\",\"findings\":[{\"file\":\"django/contrib/auth/hashers.py\",\"line\":256,\"severity\":\"major\",\"title\":\"_check_encode_args uses force_str for bytes salt and can raise decode error\",\"explanation\":\"if salt=b\\\"\\\\xff\\\\xfe\\\" or any non-utf8 bytes, force_str(salt) raises DjangoUnicodeDecodeError instead of checking for \\\"$\\\" and raising ValueError. Previous code raised TypeError for bytes, but intent is to accept bytes. Use force_bytes(salt) and check b\\\"$\\\" in ... or handle both str/bytes without decoding. Reproducer: BasePasswordHasher()._check_encode_args(\\\"pwd\\\", b\\\"\\\\xff\\\")\"},{\"file\":\"django/contrib/auth/hashers.py\",\"line\":328,\"severity\":\"major\",\"title\":\"PBKDF2PasswordHasher.encode uses force_str(password) rejecting binary passwords\",\"explanation\":\"password=force_str(password) fails for arbitrary bytes (e.g. b\\\"\\\\x80\\\\xff\\\")). pbkdf2() itself uses force_bytes and supports any bytes, as do Argon2/BCrypt/Scrypt which use force_bytes. Inconsistent: MD5/PBKDF2 reject binary passwords while others accept. Reproducer: PBKDF2PasswordHasher().encode(b\\\"\\\\x80password\\\", \\\"seasalt\\\") raises DjangoUnicodeDecodeError, but Argon2PasswordHasher().encode(b\\\"\\\\x80password\\\",\\\"seasalt\\\") succeeds.\"},{\"file\":\"django/contrib/auth/hashers.py\",\"line\":662,\"severity\":\"major\",\"title\":\"MD5PasswordHasher.encode same force_str restriction\",\"explanation\":\"Same issue as PBKDF2: force_str(password)/force_str(salt) restricts to UTF-8 bytes. Binary secrets used as passwords (common for tokens) will crash. Should preserve bytes via force_bytes for hashing and only force_str salt for storage, or use bytes concatenation directly.\"},{\"file\":\"django/contrib/auth/hashers.py\",\"line\":601,\"severity\":\"minor\",\"title\":\"Scrypt salt storage via force_str inherits same decode limitation\",\"explanation\":\"hash uses force_bytes(salt) (good) but return uses force_str(salt) for encoded form; non-utf8 bytes salt will raise DjangoUnicodeDecodeError while hash part would have succeeded. Low risk as salts are ascii, but inconsistent with encode contract.\"}]}","startedAt":"2026-10-16T07:53:04.831Z","finishedAt":"2026-10-16T07:54:11.722Z"}
{"id":"aabe2aa1-1a11-4d5f-b3f6-ed285845ee69","caseId":"django-2ce5cb0f7","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"Fix direction is correct but implementation regresses on valid existing inputs: descending (\"-field\") and random (\"?\") orderings now crash, and OrderBy wrapper / auto-group-by assumptions are handled incorrectly.","findings":[{"file":"django/db/models/sql/query.py","line":2363,"severity":"blocker","title":"Descending \"-field\" ordering crashes count()","explanation":"self.order_by stores strings with leading \"-\" (add_ordering keeps dash). Property does F(order_by).resolve_expression(q) without stripping direction, so \"-id\" -> F(\"-id\").resolve_ref tries to resolve field \"-id\" and raises FieldError. Before PR clear_ordering was unconditional and never resolved order_by, so qs.order_by(\"-id\").count() worked. Now any values().annotate().order_by(\"-...\").count() / get_aggregation crashes."},{"file":"django/db/models/sql/query.py","line":2348,"severity":"major","title":"group_by==True assumed always subset of order_by","explanation":"Property returns True when self.group_by is True, claiming order_by is necessarily subset of auto-generated model-field grouping. This is false for joins: e.g. Author.objects.annotate(c=Count('book')).order_by('book__title') groups by author PK but orders by book.title which is not in group_by and is added to GROUP BY by compiler.get_group_by(). Clearing it would change grouping and count, reproducing the original #26434 bug for annotated (non-values) queries."},{"file":"django/db/models/sql/query.py","line":2359,"severity":"major","title":"OrderBy/F expression handling breaks subset check","explanation":"For expression orderings (F('x').desc(), OrderBy(F('x')), etc.) code does order_by.resolve_expression(q) which yields OrderBy(Col(...)). self.group_by contains Col/Ref, so OrderBy != Col and issubset is always False. Subset orderings are therefore never cleared (safe but loses optimization) and more importantly the check should compare get_group_by_cols() not the wrapper itself. Also string orderings with transforms would be mishandled."},{"file":"django/db/models/sql/query.py","line":2360,"severity":"minor","title":"Random ordering \"?\" and other non-field strings not handled","explanation":"\"?\" is stored in order_by (add_ordering skips validation) and F(\"?\") will also raise FieldError in the subset check. Compiler treats OrderBy(Random()) specially with empty group_by cols, but new code tries to resolve it as a column."}]},"text":"Fix looks subtle — verifying the new ordering check actually keeps counts correct.I've pinpointed where the faulty ordering clear would break grouped counts — now I'll dig into the edge cases of this fix.I've identified how the grouping logic works — now I'll run a few quick sanity checks on descending orders and joins.Quick check: most edge cases look safe, but there's one remaining ordering detail I need to confirm before finalizing my verdict.{\"verdict\":\"request_changes\",\"summary\":\"Fix direction is correct but implementation regresses on valid existing inputs: descending (\\\"-field\\\") and random (\\\"?\\\") orderings now crash, and OrderBy wrapper / auto-group-by assumptions are handled incorrectly.\",\"findings\":[{\"file\":\"django/db/models/sql/query.py\",\"line\":2363,\"severity\":\"blocker\",\"title\":\"Descending \\\"-field\\\" ordering crashes count()\",\"explanation\":\"self.order_by stores strings with leading \\\"-\\\" (add_ordering keeps dash). Property does F(order_by).resolve_expression(q) without stripping direction, so \\\"-id\\\" -> F(\\\"-id\\\").resolve_ref tries to resolve field \\\"-id\\\" and raises FieldError. Before PR clear_ordering was unconditional and never resolved order_by, so qs.order_by(\\\"-id\\\").count() worked. Now any values().annotate().order_by(\\\"-...\\\").count() / get_aggregation crashes.\"},{\"file\":\"django/db/models/sql/query.py\",\"line\":2348,\"severity\":\"major\",\"title\":\"group_by==True assumed always subset of order_by\",\"explanation\":\"Property returns True when self.group_by is True, claiming order_by is necessarily subset of auto-generated model-field grouping. This is false for joins: e.g. Author.objects.annotate(c=Count('book')).order_by('book__title') groups by author PK but orders by book.title which is not in group_by and is added to GROUP BY by compiler.get_group_by(). Clearing it would change grouping and count, reproducing the original #26434 bug for annotated (non-values) queries.\"},{\"file\":\"django/db/models/sql/query.py\",\"line\":2359,\"severity\":\"major\",\"title\":\"OrderBy/F expression handling breaks subset check\",\"explanation\":\"For expression orderings (F('x').desc(), OrderBy(F('x')), etc.) code does order_by.resolve_expression(q) which yields OrderBy(Col(...)). self.group_by contains Col/Ref, so OrderBy != Col and issubset is always False. Subset orderings are therefore never cleared (safe but loses optimization) and more importantly the check should compare get_group_by_cols() not the wrapper itself. Also string orderings with transforms would be mishandled.\"},{\"file\":\"django/db/models/sql/query.py\",\"line\":2360,\"severity\":\"minor\",\"title\":\"Random ordering \\\"?\\\" and other non-field strings not handled\",\"explanation\":\"\\\"?\\\" is stored in order_by (add_ordering skips validation) and F(\\\"?\\\") will also raise FieldError in the subset check. Compiler treats OrderBy(Random()) specially with empty group_by cols, but new code tries to resolve it as a column.\"}]}","startedAt":"2026-10-16T07:54:11.729Z","finishedAt":"2026-10-16T07:57:17.748Z"}
{"id":"4808e14d-def4-4c88-8fbe-44be51e342c7","caseId":"django-f920937c8","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"Optimization bypasses regex for converter-less routes but breaks lazy-translated (`gettext_lazy`) RoutePattern matches: non-endpoint `path.startswith(self._route)` / `removeprefix` pass a Promise to `str` C-API (`TypeError`) and don't evaluate per-language value correctly, while endpoint equality is fragile versus explicit `str()` handling.","findings":[{"file":"django/urls/resolvers.py","line":338,"severity":"blocker","title":"Lazy-translated RoutePattern crashes / mismatches","explanation":"For `RoutePattern(\"foo/\" or _(\"translated/\"))` with `converters=={}` the new fast-path does `path.startswith(self._route)` and `path.removeprefix(self._route)`. `self._route` may be a `Promise` (result of `gettext_lazy`). `str.startswith()`/`removeprefix()` require `str`/tuple, not `Promise`, raising `TypeError: startswith first arg must be str or a tuple of str, not __proxy__` for i18n includes (`path(_(\"translated/\"), include(...))`, `_is_endpoint=False`). Even where `self._route == path` happens to work via `Promise.__eq__`, the prefix branch does not delegate to the proxy (receiver is the plain `path` string). It should evaluate the current translation once via `route = str(self._route)` and then do `route == path` / `path.startswith(route)` / `path[len(route):]` to mirror the per-language regex compilation (`LocaleRegexRouteDescriptor` uses `str(self._route)` + `get_language()`)."},{"file":"django/urls/resolvers.py","line":339,"severity":"major","title":"Endpoint fast-path should use translated string","explanation":"`if self._route == path` relies on `Promise.__eq__` only when left is the proxy. While it currently succeeds for endpoint views (`is_endpoint=True`), it is asymmetric and inconsistent with the regex path which uses `str(self._route)` for the active language. Use `str(self._route) == path` (and similarly for the prefix case) to guarantee correct language-aware matching."}]},"text":"Reviewing this route-pattern optimization — now inspecting how the new string path handles edge cases.I've mapped the core logic of the shortcut — now checking translation and edge-case coverage.{\"verdict\":\"request_changes\",\"summary\":\"Optimization bypasses regex for converter-less routes but breaks lazy-translated (`gettext_lazy`) RoutePattern matches: non-endpoint `path.startswith(self._route)` / `removeprefix` pass a Promise to `str` C-API (`TypeError`) and don't evaluate per-language value correctly, while endpoint equality is fragile versus explicit `str()` handling.\",\"findings\":[{\"file\":\"django/urls/resolvers.py\",\"line\":338,\"severity\":\"blocker\",\"title\":\"Lazy-translated RoutePattern crashes / mismatches\",\"explanation\":\"For `RoutePattern(\\\"foo/\\\" or _(\\\"translated/\\\"))` with `converters=={}` the new fast-path does `path.startswith(self._route)` and `path.removeprefix(self._route)`. `self._route` may be a `Promise` (result of `gettext_lazy`). `str.startswith()`/`removeprefix()` require `str`/tuple, not `Promise`, raising `TypeError: startswith first arg must be str or a tuple of str, not __proxy__` for i18n includes (`path(_(\\\"translated/\\\"), include(...))`, `_is_endpoint=False`). Even where `self._route == path` happens to work via `Promise.__eq__`, the prefix branch does not delegate to the proxy (receiver is the plain `path` string). It should evaluate the current translation once via `route = str(self._route)` and then do `route == path` / `path.startswith(route)` / `path[len(route):]` to mirror the per-language regex compilation (`LocaleRegexRouteDescriptor` uses `str(self._route)` + `get_language()`).\"},{\"file\":\"django/urls/resolvers.py\",\"line\":339,\"severity\":\"major\",\"title\":\"Endpoint fast-path should use translated string\",\"explanation\":\"`if self._route == path` relies on `Promise.__eq__` only when left is the proxy. While it currently succeeds for endpoint views (`is_endpoint=True`), it is asymmetric and inconsistent with the regex path which uses `str(self._route)` for the active language. Use `str(self._route) == path` (and similarly for the prefix case) to guarantee correct language-aware matching.\"}]}","startedAt":"2026-10-16T07:57:17.755Z","finishedAt":"2026-10-16T07:58:29.366Z"}
{"id":"df06498f-1d58-43da-8a62-c9b70b934ea4","caseId":"django-c075508b4","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Change fixes RFC 7231 content negotiation: specificity now outranks q, q=0 filtered, param-aware matching with direction candidate.match(accept) and wildcards both sides correctly implemented; tests cover examples.","findings":[{"file":"django/http/request.py","line":713,"severity":"minor","title":"is_all_types removal and params semantics change are breaking API","explanation":"Old MediaType.is_all_types property and MediaType.params including \"q\" are removed/changed (new params excludes q via cached_property returning copy of _params). External code using mtype.is_all_types or mutating mtype.params to affect serialization will break silently. Internally unused, but it is a public class attribute change without deprecation."}]},"text":"Skimming this media-type patch for content-negotiation regressions — now inspecting the changed request handling.I've charted the main content-negotiation fixes — now checking for lingering compatibility issues in the surrounding code.{\"verdict\":\"approve\",\"summary\":\"Change fixes RFC 7231 content negotiation: specificity now outranks q, q=0 filtered, param-aware matching with direction candidate.match(accept) and wildcards both sides correctly implemented; tests cover examples.\",\"findings\":[{\"file\":\"django/http/request.py\",\"line\":713,\"severity\":\"minor\",\"title\":\"is_all_types removal and params semantics change are breaking API\",\"explanation\":\"Old MediaType.is_all_types property and MediaType.params including \\\"q\\\" are removed/changed (new params excludes q via cached_property returning copy of _params). External code using mtype.is_all_types or mutating mtype.params to affect serialization will break silently. Internally unused, but it is a public class attribute change without deprecation.\"}]}","startedAt":"2026-10-16T07:58:29.373Z","finishedAt":"2026-10-16T07:59:48.160Z"}
{"id":"ca0dcf30-c939-433a-85af-8f915d225055","caseId":"django-c1fa3fdd0","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Fix correctly restores propagation of `for_save` in `BaseExpression.resolve_expression`, ensuring nested `Value` instances (e.g. inside `Case/When` built by `bulk_update` or inside `Coalesce`) receive `for_save=True` and thus `JSONField.get_db_prep_save()` returns SQL NULL instead of JSON null. All overrides already forward `for_save`; base class was the sole regression point introduced in 00c690ef.","findings":[]},"text":"Fix looks isolated — now verifying the propagation and its callers.Spotted where the save flag gets forwarded — next I'll map all related handlers to check for wider issues.I've reviewed the expression hierarchy — now I'll check the actual update paths to confirm the fix addresses the reported case.{\"verdict\":\"approve\",\"summary\":\"Fix correctly restores propagation of `for_save` in `BaseExpression.resolve_expression`, ensuring nested `Value` instances (e.g. inside `Case/When` built by `bulk_update` or inside `Coalesce`) receive `for_save=True` and thus `JSONField.get_db_prep_save()` returns SQL NULL instead of JSON null. All overrides already forward `for_save`; base class was the sole regression point introduced in 00c690ef.\",\"findings\":[]}","startedAt":"2026-10-16T07:59:48.167Z","finishedAt":"2026-10-16T08:01:09.206Z"}
{"id":"bb936ab0-b81e-460e-9bf1-d1eb016f61b8","caseId":"recharts-19c7abceb","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"New renderedTicks slice caches DOM-filtered ticks but introduces regressions: explicit fontSize/letterSpacing props are never wired through, panorama (Brush) support is dropped causing state collision, and useX/YAxisTicks changes return type/timing breaking existing hook consumers.","findings":[{"file":"src/cartesian/XAxis.tsx","line":299,"severity":"major","title":"fontSize/letterSpacing props declared but never forwarded to store or CartesianAxis","explanation":"XAxisProps adds fontSize/letterSpacing but SetXAxisSettings (and YAxis equivalent at YAxis.tsx:362) does not include them in the XAxisSettings dispatched to Redux, and XAxisImpl/CartesianAxis still uses internally measured fontSize state. User-provided values in <XAxis fontSize=\"12px\" /> are spread into CartesianAxis then ignored – getTicks receives DOM-measured fontSize from state, not the prop, so the documented feature does nothing."},{"file":"src/cartesian/YAxis.tsx","line":247,"severity":"major","title":"Same missing wiring for YAxis plus stale width logic interaction","explanation":"YAxisProps mirrors XAxisProps with fontSize/letterSpacing but YAxisSettingsDispatcher never persists them. Even if passed via ...allOtherProps, CartesianAxisComponent reads its own useState fontSize/letterSpacing from window.getComputedStyle and overwrites any explicit value on layerRef, so the prop cannot override DOM measurement as intended."},{"file":"src/state/selectors/axisSelectors.ts","line":2148,"severity":"major","title":"selectRenderedTicksOfAxis and selectAxisInverseTickSnapScale drop isPanorama, breaking Brush","explanation":"Old hooks called selectTicksOfAxis(state, type, id, isPanorama). New selectRenderedTicksOfAxis(state,type,id) is keyed only by (type,id) and ignores PanoramaContext. Main chart and Brush panorama both mount CartesianAxis with same axisId=0 and report via RenderedTicksReporter to the same slice key, racing and overwriting each other. useXAxisInverseTickSnapScale/useYAxisInverseTickSnapScale now snap to main-chart ticks even when called inside <Brush> panorama, regressing brush interaction."},{"file":"src/hooks.ts","line":263,"severity":"major","title":"useXAxisTicks/useYAxisTicks now async and changes return shape","explanation":"Previously returned ReadonlyArray<CartesianTickItem> (synchronously from selectTicksOfAxis, including tickCoord/payload/isShow) – now returns ReadonlyArray<TickItem> ({value,coordinate,offset,index} only) sourced from renderedTicksSlice populated in a useEffect after DOM paint. First render therefore returns undefined and later renders return a filtered subset after overlap culling; code expecting immediate full tick list or accessing payload/tickCoord breaks, and existing selectors/tests had to bump call counts 2→3 for the extra dispatch cycle."},{"file":"src/cartesian/CartesianAxis.tsx","line":318,"severity":"minor","title":"RenderedTicksReporter discards useful fields and dispatches on every identity change","explanation":"Mapping to {value,coordinate,offset,index} drops payload, tickCoord, tickSize, isShow that callers of CartesianTickItem may rely on. Effect dependency on `ticks` (new array each getTicks call) dispatches setRenderedTicks on every fontSize change/measurement, triggering an extra Redux update → re-render loop (hence widespread test updates 2→3 calls). Should memoize or equality-check before dispatch."}]},"text":"Reviewing the tick caching change — now inspecting how rendered ticks flow from CartesianAxis into the new Redux state.I've spotted several potential issues in the current implementation — now verifying the remaining gaps around prop handling.{\"verdict\":\"request_changes\",\"summary\":\"New renderedTicks slice caches DOM-filtered ticks but introduces regressions: explicit fontSize/letterSpacing props are never wired through, panorama (Brush) support is dropped causing state collision, and useX/YAxisTicks changes return type/timing breaking existing hook consumers.\",\"findings\":[{\"file\":\"src/cartesian/XAxis.tsx\",\"line\":299,\"severity\":\"major\",\"title\":\"fontSize/letterSpacing props declared but never forwarded to store or CartesianAxis\",\"explanation\":\"XAxisProps adds fontSize/letterSpacing but SetXAxisSettings (and YAxis equivalent at YAxis.tsx:362) does not include them in the XAxisSettings dispatched to Redux, and XAxisImpl/CartesianAxis still uses internally measured fontSize state. User-provided values in <XAxis fontSize=\\\"12px\\\" /> are spread into CartesianAxis then ignored – getTicks receives DOM-measured fontSize from state, not the prop, so the documented feature does nothing.\"},{\"file\":\"src/cartesian/YAxis.tsx\",\"line\":247,\"severity\":\"major\",\"title\":\"Same missing wiring for YAxis plus stale width logic interaction\",\"explanation\":\"YAxisProps mirrors XAxisProps with fontSize/letterSpacing but YAxisSettingsDispatcher never persists them. Even if passed via ...allOtherProps, CartesianAxisComponent reads its own useState fontSize/letterSpacing from window.getComputedStyle and overwrites any explicit value on layerRef, so the prop cannot override DOM measurement as intended.\"},{\"file\":\"src/state/selectors/axisSelectors.ts\",\"line\":2148,\"severity\":\"major\",\"title\":\"selectRenderedTicksOfAxis and selectAxisInverseTickSnapScale drop isPanorama, breaking Brush\",\"explanation\":\"Old hooks called selectTicksOfAxis(state, type, id, isPanorama). New selectRenderedTicksOfAxis(state,type,id) is keyed only by (type,id) and ignores PanoramaContext. Main chart and Brush panorama both mount CartesianAxis with same axisId=0 and report via RenderedTicksReporter to the same slice key, racing and overwriting each other. useXAxisInverseTickSnapScale/useYAxisInverseTickSnapScale now snap to main-chart ticks even when called inside <Brush> panorama, regressing brush interaction.\"},{\"file\":\"src/hooks.ts\",\"line\":263,\"severity\":\"major\",\"title\":\"useXAxisTicks/useYAxisTicks now async and changes return shape\",\"explanation\":\"Previously returned ReadonlyArray<CartesianTickItem> (synchronously from selectTicksOfAxis, including tickCoord/payload/isShow) – now returns ReadonlyArray<TickItem> ({value,coordinate,offset,index} only) sourced from renderedTicksSlice populated in a useEffect after DOM paint. First render therefore returns undefined and later renders return a filtered subset after overlap culling; code expecting immediate full tick list or accessing payload/tickCoord breaks, and existing selectors/tests had to bump call counts 2→3 for the extra dispatch cycle.\"},{\"file\":\"src/cartesian/CartesianAxis.tsx\",\"line\":318,\"severity\":\"minor\",\"title\":\"RenderedTicksReporter discards useful fields and dispatches on every identity change\",\"explanation\":\"Mapping to {value,coordinate,offset,index} drops payload, tickCoord, tickSize, isShow that callers of CartesianTickItem may rely on. Effect dependency on `ticks` (new array each getTicks call) dispatches setRenderedTicks on every fontSize change/measurement, triggering an extra Redux update → re-render loop (hence widespread test updates 2→3 calls). Should memoize or equality-check before dispatch.\"}]}","startedAt":"2026-10-16T08:01:09.214Z","finishedAt":"2026-10-16T08:01:58.157Z"}
{"id":"87ecb4dd-723a-4b86-be8b-512521e6dae5","caseId":"svelte-9521b9f3d","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Reordering #commit before processing next_batch prevents spurious rebase of just-created batches, correcting derived capture to also write to previous_batch and fixing value comparison (c.v vs c) eliminates false-positive rescheduling for partially overlapping batches.","findings":[]},"text":"Fix moves batch commits and derived captures to stop spurious rebasing — I'll trace the changes for correctness.{\"verdict\":\"approve\",\"summary\":\"Reordering #commit before processing next_batch prevents spurious rebase of just-created batches, correcting derived capture to also write to previous_batch and fixing value comparison (c.v vs c) eliminates false-positive rescheduling for partially overlapping batches.\",\"findings\":[]}","startedAt":"2026-10-16T08:01:58.166Z","finishedAt":"2026-10-16T08:02:52.731Z"}
{"id":"924de0f5-a05a-44b7-9daf-0e7370d6f773","caseId":"svelte-3937ec03b","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Fix correctly moves @const blocker calculation to analysis (using shared `promises_id`/declaration_count) and makes transform push a single merged thunk per const, matching new indices; scoped reset per Fragment mirrors transform behavior.","findings":[{"file":"packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js","line":95,"severity":"minor","title":"dev-mode eager check dropped for async consts","explanation":"Analysis computes `after = [$.get(id)]` for dev but the new async branch never emits it (old code wrapped assignment+after in a block thunk). This loses \"Cannot access x before initialization\" eager errors for async @const in dev. Non-async path still emits it."},{"file":"packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js","line":64,"severity":"nit","title":"Brittle count sync acknowledged","explanation":"Blocker index relies on declaration_count staying in sync with thunks pushed in transform (1 per qualifying const). TODO notes brittleness; if future change alters thunk count per const, indices will desync, as old 2-thunk→1-thunk change shows."}]},"text":"Reviewing the blocker handoff between analysis and transform — I'll verify the new bookkeeping holds up across fragments.I noticed the scoping change resets state on each fragment now — let me check how that interacts with nested contexts.The core fix looks sound, so now I'm hunting for edge cases that might have been overlooked.{\"verdict\":\"approve\",\"summary\":\"Fix correctly moves @const blocker calculation to analysis (using shared `promises_id`/declaration_count) and makes transform push a single merged thunk per const, matching new indices; scoped reset per Fragment mirrors transform behavior.\",\"findings\":[{\"file\":\"packages/svelte/src/compiler/phases/3-transform/client/visitors/ConstTag.js\",\"line\":95,\"severity\":\"minor\",\"title\":\"dev-mode eager check dropped for async consts\",\"explanation\":\"Analysis computes `after = [$.get(id)]` for dev but the new async branch never emits it (old code wrapped assignment+after in a block thunk). This loses \\\"Cannot access x before initialization\\\" eager errors for async @const in dev. Non-async path still emits it.\"},{\"file\":\"packages/svelte/src/compiler/phases/2-analyze/visitors/ConstTag.js\",\"line\":64,\"severity\":\"nit\",\"title\":\"Brittle count sync acknowledged\",\"explanation\":\"Blocker index relies on declaration_count staying in sync with thunks pushed in transform (1 per qualifying const). TODO notes brittleness; if future change alters thunk count per const, indices will desync, as old 2-thunk→1-thunk change shows.\"}]}","startedAt":"2026-10-16T08:02:52.740Z","finishedAt":"2026-10-16T08:04:02.149Z"}
{"id":"279f7185-e50c-4444-88d2-ceed15170caf","caseId":"tldraw-7375c872f","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"Right-click panning blocks to open the menu on release but the camera only jumps once: the incremental panning loop is gated on `isPointing` which is never set for the right-button path, so drags stall after the drag-threshold. Synthetic `contextmenu` dispatched as `PointerEvent` and pointer-capture transfer between MenuClickCapture and canvas are also fragile.","findings":[{"file":"packages/editor/src/lib/editor/Editor.ts","line":11084,"severity":"major","title":"Incremental right-click panning never runs – gated on `isPointing`","explanation":"`pointer_down` for button 2 with `rightClickPanning` sets `isRightPointing` but not `isPointing`. `pointer_move` first transitions to `isPanning` with a one-shot `origin→current` delta then returns. The continuation block is `if (inputs.getIsPanning() && inputs.getIsPointing())` – false for right-drag (comment claims it handles right-click). Subsequent moves fall through with no camera update, so the viewport only jumps once instead of following the drag."},{"file":"packages/editor/src/lib/components/MenuClickCapture.tsx","line":97,"severity":"major","title":"Pointer capture transferred to canvas leaks/steals moves from MenuClickCapture","explanation":"`handlePointerDown` does `setPointerCapture(menuDiv)` then `canvasEvents.onPointerDown({currentTarget:canvas})` which internally `setPointerCapture(canvas)`. Per spec only one element can capture a pointer – the second call releases capture from the menu div. Later moves/ups then target the canvas (or are handled by the body listener) while `handlePointerMove/Up` on the capture overlay stop firing, leaving the canvas capture unreleased on menu-overlay pan."},{"file":"packages/editor/src/lib/hooks/useCanvasEvents.ts","line":67,"severity":"minor","title":"Synthetic contextmenu dispatched as PointerEvent","explanation":"Both canvas and menu-overlay paths do `new PointerEvent('contextmenu',…)`. `contextmenu` is a MouseEvent; using PointerEvent is non-standard and may not propagate to Radix/listeners expecting MouseEvent properties in some browsers. Use `new MouseEvent('contextmenu',…)` with `bubbles:true`."}]},"text":"Reviewing the right-click pan flow for subtle input-handling regressions — inspecting the core pointer logic next.{\"verdict\":\"request_changes\",\"summary\":\"Right-click panning blocks to open the menu on release but the camera only jumps once: the incremental panning loop is gated on `isPointing` which is never set for the right-button path, so drags stall after the drag-threshold. Synthetic `contextmenu` dispatched as `PointerEvent` and pointer-capture transfer between MenuClickCapture and canvas are also fragile.\",\"findings\":[{\"file\":\"packages/editor/src/lib/editor/Editor.ts\",\"line\":11084,\"severity\":\"major\",\"title\":\"Incremental right-click panning never runs – gated on `isPointing`\",\"explanation\":\"`pointer_down` for button 2 with `rightClickPanning` sets `isRightPointing` but not `isPointing`. `pointer_move` first transitions to `isPanning` with a one-shot `origin→current` delta then returns. The continuation block is `if (inputs.getIsPanning() && inputs.getIsPointing())` – false for right-drag (comment claims it handles right-click). Subsequent moves fall through with no camera update, so the viewport only jumps once instead of following the drag.\"},{\"file\":\"packages/editor/src/lib/components/MenuClickCapture.tsx\",\"line\":97,\"severity\":\"major\",\"title\":\"Pointer capture transferred to canvas leaks/steals moves from MenuClickCapture\",\"explanation\":\"`handlePointerDown` does `setPointerCapture(menuDiv)` then `canvasEvents.onPointerDown({currentTarget:canvas})` which internally `setPointerCapture(canvas)`. Per spec only one element can capture a pointer – the second call releases capture from the menu div. Later moves/ups then target the canvas (or are handled by the body listener) while `handlePointerMove/Up` on the capture overlay stop firing, leaving the canvas capture unreleased on menu-overlay pan.\"},{\"file\":\"packages/editor/src/lib/hooks/useCanvasEvents.ts\",\"line\":67,\"severity\":\"minor\",\"title\":\"Synthetic contextmenu dispatched as PointerEvent\",\"explanation\":\"Both canvas and menu-overlay paths do `new PointerEvent('contextmenu',…)`. `contextmenu` is a MouseEvent; using PointerEvent is non-standard and may not propagate to Radix/listeners expecting MouseEvent properties in some browsers. Use `new MouseEvent('contextmenu',…)` with `bubbles:true`.\"}]}","startedAt":"2026-10-16T08:04:02.158Z","finishedAt":"2026-10-16T08:04:52.796Z"}
{"id":"4c5420fe-fa36-4e29-9696-f724edc5ec95","caseId":"tldraw-67f5b0896","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"PR claims minor bugfixes but introduces mutating Vec methods, ineffective Rectangle SVG fix, and breaking API privatization that will break existing consumers.","findings":[{"file":"packages/editor/src/lib/primitives/Vec.ts","line":191,"severity":"blocker","title":"Vec.uni() instance now mutates caller instead of returning new Vec","explanation":"Old instance `uni()` delegated to `Vec.Uni(this)` which returns a new Vec (pure). New implementation divides `this` in place (`this.x/=l`). Any caller doing `const u = a.uni(); // expect a unchanged` or chaining `Vec.Sub(a,b).uni()` that relied on Sub's result being disposable but later reused `a` will now see `a` mutated. Also breaks `tan()` which now does `this.sub(V).uni()` mutating the receiver instead of pure `Vec.Tan(this,V)`."},{"file":"packages/editor/src/lib/primitives/Vec.ts","line":248,"severity":"blocker","title":"Vec.toFixed() instance now mutates geometry vectors","explanation":"Old `toFixed()` returned `Vec.ToFixed(this)` (new Vec). New mutates `this` via `toFixed(this.x)`. Call sites like `Edge2d.getSvgPathData(): start.toFixed()` and `Arc2d/CubicBezier` now permanently round the stored geometry points (`_start`, `_a`, etc.) to 2-decimal precision on first SVG export, corrupting hit-testing and future renders."},{"file":"packages/editor/src/lib/primitives/Vec.ts","line":199,"severity":"major","title":"Vec.tan() now mutates receiver","explanation":"Previously `tan(V)` returned `Vec.Tan(this,V)` (pure, creates new Vec). Now `return this.sub(V).uni()` mutates `this` in place. Callers expecting non-mutating tangent (e.g., `pos.tan(other)` preserving `pos`) will have `pos` overwritten with direction."},{"file":"packages/editor/src/lib/primitives/geometry/Rectangle2d.ts","line":41,"severity":"major","title":"Rectangle SVG negative-zero fix is ineffective","explanation":"`getSvgPathData` destructures `{_x:x,_y:y,_w:w,_h:h}=this` before calling `this.negativeZeroFix()`, so the returned string `M${x},${y} h${w}...` still contains `-0` (or un-fixed values). Fix must run before destructuring or use `this._x` directly after fixing."},{"file":"packages/editor/src/lib/primitives/geometry/Polyline2d.ts","line":15,"severity":"major","title":"New throws on <2 points break backward compat","explanation":"Constructor now throws if `points.length < 2` (and `Polygon2d` <3). Previously callers could construct degenerate geometry (used during shape creation/interpolation). This is a runtime regression – existing shapes or tests passing empty/singleton arrays will now crash instead of producing empty geometry."},{"file":"packages/editor/src/lib/primitives/geometry/Arc2d.ts","line":6,"severity":"major","title":"Public geometry fields made private without replacement","explanation":"All public fields (`_center,radius,start,end,largeArcFlag,sweepFlag,measure,angleStart/End` and equivalents in Circle2d, Edge2d, Rectangle2d, Stadium2d, Ellipse2d, etc.) were changed to `private _*`. They were part of public exports (api-report.md) – external code/tests accessing `rect.w`/`circle.radius`/`edge.start` etc. now gets undefined at runtime and TypeScript errors. This is an unintended breaking API change for a bugfix PR."}]},"text":"Reviewing your geometry bug fixes — now inspecting the diff for hidden regressions and API changes.I've spotted several subtle API and mutation issues — now verifying how widely they're used across the codebase.{\"verdict\":\"request_changes\",\"summary\":\"PR claims minor bugfixes but introduces mutating Vec methods, ineffective Rectangle SVG fix, and breaking API privatization that will break existing consumers.\",\"findings\":[{\"file\":\"packages/editor/src/lib/primitives/Vec.ts\",\"line\":191,\"severity\":\"blocker\",\"title\":\"Vec.uni() instance now mutates caller instead of returning new Vec\",\"explanation\":\"Old instance `uni()` delegated to `Vec.Uni(this)` which returns a new Vec (pure). New implementation divides `this` in place (`this.x/=l`). Any caller doing `const u = a.uni(); // expect a unchanged` or chaining `Vec.Sub(a,b).uni()` that relied on Sub's result being disposable but later reused `a` will now see `a` mutated. Also breaks `tan()` which now does `this.sub(V).uni()` mutating the receiver instead of pure `Vec.Tan(this,V)`.\"},{\"file\":\"packages/editor/src/lib/primitives/Vec.ts\",\"line\":248,\"severity\":\"blocker\",\"title\":\"Vec.toFixed() instance now mutates geometry vectors\",\"explanation\":\"Old `toFixed()` returned `Vec.ToFixed(this)` (new Vec). New mutates `this` via `toFixed(this.x)`. Call sites like `Edge2d.getSvgPathData(): start.toFixed()` and `Arc2d/CubicBezier` now permanently round the stored geometry points (`_start`, `_a`, etc.) to 2-decimal precision on first SVG export, corrupting hit-testing and future renders.\"},{\"file\":\"packages/editor/src/lib/primitives/Vec.ts\",\"line\":199,\"severity\":\"major\",\"title\":\"Vec.tan() now mutates receiver\",\"explanation\":\"Previously `tan(V)` returned `Vec.Tan(this,V)` (pure, creates new Vec). Now `return this.sub(V).uni()` mutates `this` in place. Callers expecting non-mutating tangent (e.g., `pos.tan(other)` preserving `pos`) will have `pos` overwritten with direction.\"},{\"file\":\"packages/editor/src/lib/primitives/geometry/Rectangle2d.ts\",\"line\":41,\"severity\":\"major\",\"title\":\"Rectangle SVG negative-zero fix is ineffective\",\"explanation\":\"`getSvgPathData` destructures `{_x:x,_y:y,_w:w,_h:h}=this` before calling `this.negativeZeroFix()`, so the returned string `M${x},${y} h${w}...` still contains `-0` (or un-fixed values). Fix must run before destructuring or use `this._x` directly after fixing.\"},{\"file\":\"packages/editor/src/lib/primitives/geometry/Polyline2d.ts\",\"line\":15,\"severity\":\"major\",\"title\":\"New throws on <2 points break backward compat\",\"explanation\":\"Constructor now throws if `points.length < 2` (and `Polygon2d` <3). Previously callers could construct degenerate geometry (used during shape creation/interpolation). This is a runtime regression – existing shapes or tests passing empty/singleton arrays will now crash instead of producing empty geometry.\"},{\"file\":\"packages/editor/src/lib/primitives/geometry/Arc2d.ts\",\"line\":6,\"severity\":\"major\",\"title\":\"Public geometry fields made private without replacement\",\"explanation\":\"All public fields (`_center,radius,start,end,largeArcFlag,sweepFlag,measure,angleStart/End` and equivalents in Circle2d, Edge2d, Rectangle2d, Stadium2d, Ellipse2d, etc.) were changed to `private _*`. They were part of public exports (api-report.md) – external code/tests accessing `rect.w`/`circle.radius`/`edge.start` etc. now gets undefined at runtime and TypeScript errors. This is an unintended breaking API change for a bugfix PR.\"}]}","startedAt":"2026-10-16T08:04:52.806Z","finishedAt":"2026-10-16T08:05:34.953Z"}
{"id":"aceb44d4-c370-4dd9-a841-52c8489f6a7b","caseId":"tiptap-404c68334","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Fix correctly normalizes AllSelection to an inner TextSelection so lift/convert works for a sole top-level list; can() dry-run and existing intra-list/toggle paths are preserved.","findings":[{"file":"packages/core/src/commands/toggleList.ts","line":121,"severity":"minor","title":"AllSelection detection via from/to may mis-classify","explanation":"Code uses `selection.from===0 && selection.to===state.doc.content.size` instead of `instanceof AllSelection`. A dragged full-doc TextSelection has same range and will incorrectly take the whole-doc list path, though behavior is likely equivalent."},{"file":"packages/core/src/commands/toggleList.ts","line":122,"severity":"nit","title":"Uses internal Fragment array","explanation":"`state.doc.content.content` accesses private PM Fragment internals; public API is `doc.childCount`/`doc.firstChild`/`doc.content.forEach`. Works today but fragile across PM versions."},{"file":"packages/core/src/commands/toggleList.ts","line":88,"severity":"nit","title":"Hard-coded positions assume doc-start list","explanation":"`from=1, to=list.nodeSize-1` is only valid when list is the sole top-level child at pos 0. Guarded by `soleTopLevelNode` check so safe, but no defensive resolvable-position validation."}]},"text":"Reviewing the AllSelection list-toggle fix — I'll verify how the revised command handles whole-document and nested lists.My initial scan of the selection logic looks complete — now I'll dig into the edge cases around the lift operation.{\"verdict\":\"approve\",\"summary\":\"Fix correctly normalizes AllSelection to an inner TextSelection so lift/convert works for a sole top-level list; can() dry-run and existing intra-list/toggle paths are preserved.\",\"findings\":[{\"file\":\"packages/core/src/commands/toggleList.ts\",\"line\":121,\"severity\":\"minor\",\"title\":\"AllSelection detection via from/to may mis-classify\",\"explanation\":\"Code uses `selection.from===0 && selection.to===state.doc.content.size` instead of `instanceof AllSelection`. A dragged full-doc TextSelection has same range and will incorrectly take the whole-doc list path, though behavior is likely equivalent.\"},{\"file\":\"packages/core/src/commands/toggleList.ts\",\"line\":122,\"severity\":\"nit\",\"title\":\"Uses internal Fragment array\",\"explanation\":\"`state.doc.content.content` accesses private PM Fragment internals; public API is `doc.childCount`/`doc.firstChild`/`doc.content.forEach`. Works today but fragile across PM versions.\"},{\"file\":\"packages/core/src/commands/toggleList.ts\",\"line\":88,\"severity\":\"nit\",\"title\":\"Hard-coded positions assume doc-start list\",\"explanation\":\"`from=1, to=list.nodeSize-1` is only valid when list is the sole top-level child at pos 0. Guarded by `soleTopLevelNode` check so safe, but no defensive resolvable-position validation.\"}]}","startedAt":"2026-10-16T08:05:34.962Z","finishedAt":"2026-10-16T08:07:00.280Z"}
{"id":"0a83c71c-b618-44e3-944c-af7569777b72","caseId":"tiptap-c0190bdc3","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"PR expands `EditorOptions.element` typing to `Element|{mount}|callback|null` and rewrites React/Vue adapters to move `editor.view.dom` directly, but `Editor.mount/createView` was not updated to handle the new union so the promised API crashes, and the adapter changes introduce `view.dom` access via a throwing proxy when the view is not mounted/destroyed.","findings":[{"file":"packages/core/src/Editor.ts","line":154,"severity":"major","title":"New `element` union not implemented in `mount`/`createView`","explanation":"Types now advertises `{mount: HTMLElement}` and `(el)=>void` as valid `element` values (changeset hip-windows-retire.md) but `mount(el)` and `createView(element)` still do `new EditorView(element,...)` directly. Passing the new shapes will throw/type-error at runtime; docs promise behavior that does not exist."},{"file":"packages/core/src/types.ts","line":270,"severity":"major","title":"Type over-promises mounting modes","explanation":"Changing `element: Element|null` to `Element|{mount:HTMLElement}|((editor:HTMLElement)=>void)|null` widens the public API without runtime support. Existing `Element|null` callers unaffected, but any consumer following the new docs/changelog will hit a crash in core."},{"file":"packages/react/src/EditorContent.tsx","line":124,"severity":"blocker","title":"`editor.view.dom` access can throw when view unavailable","explanation":"When unmounted/destroyed `editor.view` is a Proxy that throws on unknown properties like `dom`. New code does `element.append(editor.view.dom)` and `editor.view.dom?.firstChild` without guarding `isDestroyed`/availability. Old code used `editor.options.element` which is always safe. This crashes e.g. componentWillUnmount after external destroy."},{"file":"packages/react/src/EditorContent.tsx","line":179,"severity":"major","title":"Guard changed from wrapper child to content child","explanation":"Old guard `!editor.options.element?.firstChild` checked whether the mount wrapper contains the ProseMirror view (i.e., view was actually attached). New guard `!editor.view.dom?.firstChild` checks whether the editable content itself has a child (first paragraph/text). For an empty doc or a view moved/destroyed the conditions differ, causing missed re-parenting or false early-return."},{"file":"packages/vue-2/src/EditorContent.ts","line":28,"severity":"blocker","title":"Same throwing `view.dom` access in Vue 2 adapter","explanation":"Inside `$nextTick` handler and `beforeDestroy`, new code accesses `editor.view.dom?.firstChild` and `element.append(editor.view.dom)`. If the tick fires after destroy/unmount, the Proxy throws instead of safely no-oping via `options.element`."},{"file":"packages/vue-3/src/EditorContent.ts","line":25,"severity":"blocker","title":"Same throwing `view.dom` access in Vue 3 adapter","explanation":"`watchEffect` nextTick callback and teardown now depend on `editor.view.dom`. Same proxy-throw regression as React/Vue2; previously guarded by `editor.options.element`."},{"file":"packages/vue-3/src/useEditor.ts","line":15,"severity":"major","title":"Clone/replace logic now clones `view.dom` not wrapper and can throw","explanation":"Was `nodes = options.element` (wrapper) cloned and replaced in its parent to preserve visible HTML after destroy. Now `nodes = view.dom` so only inner ProseMirror node is cloned and replaced inside the wrapper, changing DOM output and leaving wrapper semantics different. Also `editor.value?.view.dom` throws when view not yet mounted/destroyed (proxy), whereas `options.element` was safe optional chain."}]},"text":"Reviewing the editor mount typing fix — checking whether the new `element` modes break existing integrations.I've mapped how `element` is used throughout the codebase — now digging into the remaining framework adapters.{\"verdict\":\"request_changes\",\"summary\":\"PR expands `EditorOptions.element` typing to `Element|{mount}|callback|null` and rewrites React/Vue adapters to move `editor.view.dom` directly, but `Editor.mount/createView` was not updated to handle the new union so the promised API crashes, and the adapter changes introduce `view.dom` access via a throwing proxy when the view is not mounted/destroyed.\",\"findings\":[{\"file\":\"packages/core/src/Editor.ts\",\"line\":154,\"severity\":\"major\",\"title\":\"New `element` union not implemented in `mount`/`createView`\",\"explanation\":\"Types now advertises `{mount: HTMLElement}` and `(el)=>void` as valid `element` values (changeset hip-windows-retire.md) but `mount(el)` and `createView(element)` still do `new EditorView(element,...)` directly. Passing the new shapes will throw/type-error at runtime; docs promise behavior that does not exist.\"},{\"file\":\"packages/core/src/types.ts\",\"line\":270,\"severity\":\"major\",\"title\":\"Type over-promises mounting modes\",\"explanation\":\"Changing `element: Element|null` to `Element|{mount:HTMLElement}|((editor:HTMLElement)=>void)|null` widens the public API without runtime support. Existing `Element|null` callers unaffected, but any consumer following the new docs/changelog will hit a crash in core.\"},{\"file\":\"packages/react/src/EditorContent.tsx\",\"line\":124,\"severity\":\"blocker\",\"title\":\"`editor.view.dom` access can throw when view unavailable\",\"explanation\":\"When unmounted/destroyed `editor.view` is a Proxy that throws on unknown properties like `dom`. New code does `element.append(editor.view.dom)` and `editor.view.dom?.firstChild` without guarding `isDestroyed`/availability. Old code used `editor.options.element` which is always safe. This crashes e.g. componentWillUnmount after external destroy.\"},{\"file\":\"packages/react/src/EditorContent.tsx\",\"line\":179,\"severity\":\"major\",\"title\":\"Guard changed from wrapper child to content child\",\"explanation\":\"Old guard `!editor.options.element?.firstChild` checked whether the mount wrapper contains the ProseMirror view (i.e., view was actually attached). New guard `!editor.view.dom?.firstChild` checks whether the editable content itself has a child (first paragraph/text). For an empty doc or a view moved/destroyed the conditions differ, causing missed re-parenting or false early-return.\"},{\"file\":\"packages/vue-2/src/EditorContent.ts\",\"line\":28,\"severity\":\"blocker\",\"title\":\"Same throwing `view.dom` access in Vue 2 adapter\",\"explanation\":\"Inside `$nextTick` handler and `beforeDestroy`, new code accesses `editor.view.dom?.firstChild` and `element.append(editor.view.dom)`. If the tick fires after destroy/unmount, the Proxy throws instead of safely no-oping via `options.element`.\"},{\"file\":\"packages/vue-3/src/EditorContent.ts\",\"line\":25,\"severity\":\"blocker\",\"title\":\"Same throwing `view.dom` access in Vue 3 adapter\",\"explanation\":\"`watchEffect` nextTick callback and teardown now depend on `editor.view.dom`. Same proxy-throw regression as React/Vue2; previously guarded by `editor.options.element`.\"},{\"file\":\"packages/vue-3/src/useEditor.ts\",\"line\":15,\"severity\":\"major\",\"title\":\"Clone/replace logic now clones `view.dom` not wrapper and can throw\",\"explanation\":\"Was `nodes = options.element` (wrapper) cloned and replaced in its parent to preserve visible HTML after destroy. Now `nodes = view.dom` so only inner ProseMirror node is cloned and replaced inside the wrapper, changing DOM output and leaving wrapper semantics different. Also `editor.value?.view.dom` throws when view not yet mounted/destroyed (proxy), whereas `options.element` was safe optional chain.\"}]}","startedAt":"2026-10-16T08:07:00.290Z","finishedAt":"2026-10-16T08:07:55.037Z"}
{"id":"b5de5da6-4217-435d-9c8f-9ed02bea65ee","caseId":"prettier-592721622","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"New isLineBreakAmbiguous is called without null-guard and crashes on TypeError, and its reuse in the isLink/code path uses wrong adjacency.","findings":[{"file":"src/language-markdown/print/whitespace.js","line":306,"severity":"blocker","title":"TypeError on null previous/next in isLineBreakAmbiguous","explanation":"isLineBreakAmbiguous checks Boolean(previous?.isCJ) || Boolean(next?.isCJ) but then accesses previous.kind / next.kind without optional chaining. AstPath.previous/next can be null for whitespace at document boundaries (and defensively elsewhere – isBreakable/lineBreakCanBeConvertedToSpace both guard with !previous||!next). A text starting/ending with an ambiguous '\\n' (previous or next null, other side isCJ true) will throw Cannot read properties of null. Fix: use previous?.kind / next?.kind and handle null."},{"file":"src/language-markdown/print/whitespace.js","line":255,"severity":"major","title":"isLineBreakAmbiguous applied to wrong path for link-reference content","explanation":"printWhitespace(path, ...) is called from mdast.js shouldRemainTheSameContent with the outer linkReference/imageReference path plus splitText tokens; path.previous/next are outer siblings, not the adjacent WordNodes of the embedded whitespace. Evaluating isLineBreakAmbiguous on that outer path can spuriously force hardline (or miss it). Should bypass the ambiguous-preserve logic when isLink===true or only evaluate it for real whitespace nodes (printWhitespaceNode)."}]},"text":"Reviewing the line-break handling change — inspecting the affected markdown logic for regressions.I’ve spotted a potential edge case in the newline preservation logic — now hunting down related definitions and coverage.{\"verdict\":\"request_changes\",\"summary\":\"New isLineBreakAmbiguous is called without null-guard and crashes on TypeError, and its reuse in the isLink/code path uses wrong adjacency.\",\"findings\":[{\"file\":\"src/language-markdown/print/whitespace.js\",\"line\":306,\"severity\":\"blocker\",\"title\":\"TypeError on null previous/next in isLineBreakAmbiguous\",\"explanation\":\"isLineBreakAmbiguous checks Boolean(previous?.isCJ) || Boolean(next?.isCJ) but then accesses previous.kind / next.kind without optional chaining. AstPath.previous/next can be null for whitespace at document boundaries (and defensively elsewhere – isBreakable/lineBreakCanBeConvertedToSpace both guard with !previous||!next). A text starting/ending with an ambiguous '\\\\n' (previous or next null, other side isCJ true) will throw Cannot read properties of null. Fix: use previous?.kind / next?.kind and handle null.\"},{\"file\":\"src/language-markdown/print/whitespace.js\",\"line\":255,\"severity\":\"major\",\"title\":\"isLineBreakAmbiguous applied to wrong path for link-reference content\",\"explanation\":\"printWhitespace(path, ...) is called from mdast.js shouldRemainTheSameContent with the outer linkReference/imageReference path plus splitText tokens; path.previous/next are outer siblings, not the adjacent WordNodes of the embedded whitespace. Evaluating isLineBreakAmbiguous on that outer path can spuriously force hardline (or miss it). Should bypass the ambiguous-preserve logic when isLink===true or only evaluate it for real whitespace nodes (printWhitespaceNode).\"}]}","startedAt":"2026-10-16T08:07:55.047Z","finishedAt":"2026-10-16T08:08:48.074Z"}
{"id":"a3541848-81c6-4638-98ab-84646e16c3ba","caseId":"core-3b561db4a","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Fix correctly prevents the discriminant symbol from polluting value unions by changing it to required `never` (so indexed access yields `never` which disappears in unions) and inverts the UnwrapRefSimple branch accordingly; behavioral equivalence for ref-unwrapping is preserved.","findings":[]},"text":"Inspecting the marker patch for type leaks — checking surrounding logic and regression risk.I've examined the reactivity fixes and their downstream effects — now I'll surface my final assessment.{\"verdict\":\"approve\",\"summary\":\"Fix correctly prevents the discriminant symbol from polluting value unions by changing it to required `never` (so indexed access yields `never` which disappears in unions) and inverts the UnwrapRefSimple branch accordingly; behavioral equivalence for ref-unwrapping is preserved.\",\"findings\":[]}","startedAt":"2026-10-16T08:08:48.084Z","finishedAt":"2026-10-16T08:09:37.391Z"}
{"id":"d4aaa33e-6e9f-47cf-9339-6ba8e2b7c711","caseId":"core-862f11ee0","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"SSR scope cleanup is correct for isolated apps but introduces error-masking in `renderToString` and retains the underlying cross-request race via shared `appContext.provides`.","findings":[{"file":"packages/server-renderer/src/renderToString.ts","line":97,"severity":"major","title":"finally masking original render error","explanation":"`cleanupContext` throws first dispose error from `finally{}`. If `renderComponentVNode`/`unrollBuffer`/`resolveTeleports` rejected and a `onScopeDispose`/`unwatch` also throws, JS `finally` semantics discard the original render failure. `renderToSimpleStream` preserves the original error via try/catch around `finalize()`, but `renderToString` does not, losing diagnostics."},{"file":"packages/server-renderer/src/render.ts","line":133,"severity":"major","title":"scope ownership tied to mutable appContext.provides","explanation":"Scopes are registered via `instance.appContext.provides[ssrContextKey]`. `input.provide()` mutates the single `appContext.provides` object, so two concurrent `renderToString`/`renderToStream` calls reusing the same `App` instance race: the second `provide` overwrites the first, and any child `createComponentInstance` after an async `setup` gap reads the latest value and pushes its `instance.scope` onto the wrong `SSRContext.__instanceScopes`. Separate apps (as in the added test) work, but same-app concurrency leaks/double-frees."},{"file":"packages/server-renderer/src/render.ts","line":65,"severity":"minor","title":"cleanup leaves stale provide reference","explanation":"`cleanupContext` clears array lengths but never removes the `ssrContextKey` provide from `appContext.provides`. Reusing an `App` sequentially keeps a live reference to the previous `SSRContext` (with `teleports`/`__instanceScopes`) until overwritten, retaining memory and potentially exposing stale teleports on next render if provide is read before overwrite."}]},"text":"Reviewing this SSR cleanup — inspecting scope tracking for correctness across concurrent renders.I've mapped the scope hierarchy — now I'll dig deeper into how it behaves during server rendering.{\"verdict\":\"request_changes\",\"summary\":\"SSR scope cleanup is correct for isolated apps but introduces error-masking in `renderToString` and retains the underlying cross-request race via shared `appContext.provides`.\",\"findings\":[{\"file\":\"packages/server-renderer/src/renderToString.ts\",\"line\":97,\"severity\":\"major\",\"title\":\"finally masking original render error\",\"explanation\":\"`cleanupContext` throws first dispose error from `finally{}`. If `renderComponentVNode`/`unrollBuffer`/`resolveTeleports` rejected and a `onScopeDispose`/`unwatch` also throws, JS `finally` semantics discard the original render failure. `renderToSimpleStream` preserves the original error via try/catch around `finalize()`, but `renderToString` does not, losing diagnostics.\"},{\"file\":\"packages/server-renderer/src/render.ts\",\"line\":133,\"severity\":\"major\",\"title\":\"scope ownership tied to mutable appContext.provides\",\"explanation\":\"Scopes are registered via `instance.appContext.provides[ssrContextKey]`. `input.provide()` mutates the single `appContext.provides` object, so two concurrent `renderToString`/`renderToStream` calls reusing the same `App` instance race: the second `provide` overwrites the first, and any child `createComponentInstance` after an async `setup` gap reads the latest value and pushes its `instance.scope` onto the wrong `SSRContext.__instanceScopes`. Separate apps (as in the added test) work, but same-app concurrency leaks/double-frees.\"},{\"file\":\"packages/server-renderer/src/render.ts\",\"line\":65,\"severity\":\"minor\",\"title\":\"cleanup leaves stale provide reference\",\"explanation\":\"`cleanupContext` clears array lengths but never removes the `ssrContextKey` provide from `appContext.provides`. Reusing an `App` sequentially keeps a live reference to the previous `SSRContext` (with `teleports`/`__instanceScopes`) until overwritten, retaining memory and potentially exposing stale teleports on next render if provide is read before overwrite.\"}]}","startedAt":"2026-10-16T08:09:37.402Z","finishedAt":"2026-10-16T08:10:40.794Z"}
{"id":"e66e2cf8-47b0-4d65-b837-1c114707205b","caseId":"core-e6544ac29","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"Fix only handles outermost TSAsExpression/TSTypeAssertion/etc. It drops *all* leading/trailing text for that case, deleting operators like `typeof`/`await`/`(`, and still leaks `as …`/` <T>` when the cast is a sub-expression (binary/member/call).","findings":[{"file":"packages/compiler-core/src/transforms/transformExpression.ts","line":351,"severity":"major","title":"Over-aggressive stripping for TS root loses real code","explanation":"When `ast.type ∈ TS_NODE_TYPES` the code skips the whole `leadingText` before the first identifier and the whole `trailingText` after the last (`!(isTSNode&&i===0)` and `&&!isTSNode`). For `typeof foo as string`, `await foo as Promise<void>`, `!foo as boolean` or even `(a+b) as number` the `TSAsExpression` wraps an expression that already contains `typeof `/`await `/`!`/`(` before the first id and `)`. Those are discarded. Old code leaked ` as string` but preserved the prefix; new code silently changes semantics to `_ctx.foo`. Same for first/last `(`/`)` grouping that should be kept."},{"file":"packages/compiler-core/src/transforms/transformExpression.ts","line":379,"severity":"major","title":"Type casts in non-root position still leak into JS output","explanation":"`isTSNode` is only checked against the top-level `ast.type`. Expressions where a cast is inside another expr still emit TS syntax: `foo as number + bar` → gap ` as number + ` → `_ctx.foo as number + _ctx.bar`; `(foo as any).bar`, `(foo as any)(e)`, `x ? y as T : z`, `fn(a as string,b)` all leak. `walkIdentifiers` correctly ignores type ids, but `processExpression` re-inserts the raw slice between ids unchanged. Result is invalid JS at runtime. Parser wrapping/Program vs Expression differences (asRawStatements/asParams, ParenthesizedExpression) make the `ast.type` check brittle and ineffective for those cases."}]},"text":"Checking the expression transform patch for TypeScript assertion handling.I’ve seen the snapshot expectation — now I’ll trace how type nodes are currently stripped.{\"verdict\":\"request_changes\",\"summary\":\"Fix only handles outermost TSAsExpression/TSTypeAssertion/etc. It drops *all* leading/trailing text for that case, deleting operators like `typeof`/`await`/`(`, and still leaks `as …`/` <T>` when the cast is a sub-expression (binary/member/call).\",\"findings\":[{\"file\":\"packages/compiler-core/src/transforms/transformExpression.ts\",\"line\":351,\"severity\":\"major\",\"title\":\"Over-aggressive stripping for TS root loses real code\",\"explanation\":\"When `ast.type ∈ TS_NODE_TYPES` the code skips the whole `leadingText` before the first identifier and the whole `trailingText` after the last (`!(isTSNode&&i===0)` and `&&!isTSNode`). For `typeof foo as string`, `await foo as Promise<void>`, `!foo as boolean` or even `(a+b) as number` the `TSAsExpression` wraps an expression that already contains `typeof `/`await `/`!`/`(` before the first id and `)`. Those are discarded. Old code leaked ` as string` but preserved the prefix; new code silently changes semantics to `_ctx.foo`. Same for first/last `(`/`)` grouping that should be kept.\"},{\"file\":\"packages/compiler-core/src/transforms/transformExpression.ts\",\"line\":379,\"severity\":\"major\",\"title\":\"Type casts in non-root position still leak into JS output\",\"explanation\":\"`isTSNode` is only checked against the top-level `ast.type`. Expressions where a cast is inside another expr still emit TS syntax: `foo as number + bar` → gap ` as number + ` → `_ctx.foo as number + _ctx.bar`; `(foo as any).bar`, `(foo as any)(e)`, `x ? y as T : z`, `fn(a as string,b)` all leak. `walkIdentifiers` correctly ignores type ids, but `processExpression` re-inserts the raw slice between ids unchanged. Result is invalid JS at runtime. Parser wrapping/Program vs Expression differences (asRawStatements/asParams, ParenthesizedExpression) make the `ast.type` check brittle and ineffective for those cases.\"}]}","startedAt":"2026-10-16T08:10:40.805Z","finishedAt":"2026-10-16T08:11:55.449Z"}
{"id":"b8e25fbc-3d4c-4840-931d-8eafe045a07f","caseId":"ant-design-e81da48f8","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"Anchor.handleScrollTo never cancels the previous animation because activeLinkRef is updated before the comparison, and it still starts a new animation on same-link clicks; the cancelled animation also never clears animating.current and has no unmount cleanup, permanently disabling scroll handling.","findings":[{"file":"components/anchor/Anchor.tsx","line":272,"severity":"blocker","title":"Cancellation check dead due to setCurrentActiveLink ordering","explanation":"handleScrollTo calls setCurrentActiveLink(link) on line 272 which sets activeLinkRef.current = link, then checks animating.current && activeLinkRef.current !== link on line 282. After the update the condition is always false for normal links (only true when getCurrentAnchor remaps), so rapid clicks on different anchors never cancel the previous raf loop. Two concurrent scrollTo loops fight for window.scrollTo and final Y is non-deterministic."},{"file":"components/anchor/Anchor.tsx","line":282,"severity":"major","title":"Same-link click during animation still starts duplicate scrollTo","explanation":"The intended fix (and new test 'should not scroll when clicking the same active link') requires an early-return when activeLinkRef.current === link while animating. Current code only cancels when different, otherwise falls through to compute y and call scrollTo again, overwriting scrollRequestId without cancelling the prior raf. Test expectation fails and overlapping animations conflict."},{"file":"components/anchor/Anchor.tsx","line":291,"severity":"major","title":"animating flag leaked after cancel and no unmount cleanup","explanation":"scrollTo cancel (raf.cancel) prevents the callback that does animating.current=false from ever running. When the previous animation is cancelled and replaced, animating stays true until next completes - benign - but if component unmounts mid-animation there is no effect cleanup that calls scrollRequestId.current?.(). handleScroll is gated by animating.current, so it remains blocked forever and the outstanding raf leaks. The test 'properly cleanup' asserts nothing because cleanup was not implemented."},{"file":"components/_util/scrollTo.ts","line":42,"severity":"minor","title":"Return-type changed to cancel function without updating callers/mocks","explanation":"scrollTo previously returned void, now returns () => void. Existing mocks like components/table/__tests__/Table.pagination.test.tsx use mockReturnValue(null) assuming void; with strict types this leaks null into scrollRequestId.current and optional chaining hides errors. More importantly external users of scrollTo that ignored return now silently hold no way to clean up, and types/docs not updated."}]},"text":"Inspecting this anchor scroll fix for animation conflicts and regressions.Found a key ordering bug blocking the cancellation; now checking the remaining scroll interactions.{\"verdict\":\"request_changes\",\"summary\":\"Anchor.handleScrollTo never cancels the previous animation because activeLinkRef is updated before the comparison, and it still starts a new animation on same-link clicks; the cancelled animation also never clears animating.current and has no unmount cleanup, permanently disabling scroll handling.\",\"findings\":[{\"file\":\"components/anchor/Anchor.tsx\",\"line\":272,\"severity\":\"blocker\",\"title\":\"Cancellation check dead due to setCurrentActiveLink ordering\",\"explanation\":\"handleScrollTo calls setCurrentActiveLink(link) on line 272 which sets activeLinkRef.current = link, then checks animating.current && activeLinkRef.current !== link on line 282. After the update the condition is always false for normal links (only true when getCurrentAnchor remaps), so rapid clicks on different anchors never cancel the previous raf loop. Two concurrent scrollTo loops fight for window.scrollTo and final Y is non-deterministic.\"},{\"file\":\"components/anchor/Anchor.tsx\",\"line\":282,\"severity\":\"major\",\"title\":\"Same-link click during animation still starts duplicate scrollTo\",\"explanation\":\"The intended fix (and new test 'should not scroll when clicking the same active link') requires an early-return when activeLinkRef.current === link while animating. Current code only cancels when different, otherwise falls through to compute y and call scrollTo again, overwriting scrollRequestId without cancelling the prior raf. Test expectation fails and overlapping animations conflict.\"},{\"file\":\"components/anchor/Anchor.tsx\",\"line\":291,\"severity\":\"major\",\"title\":\"animating flag leaked after cancel and no unmount cleanup\",\"explanation\":\"scrollTo cancel (raf.cancel) prevents the callback that does animating.current=false from ever running. When the previous animation is cancelled and replaced, animating stays true until next completes - benign - but if component unmounts mid-animation there is no effect cleanup that calls scrollRequestId.current?.(). handleScroll is gated by animating.current, so it remains blocked forever and the outstanding raf leaks. The test 'properly cleanup' asserts nothing because cleanup was not implemented.\"},{\"file\":\"components/_util/scrollTo.ts\",\"line\":42,\"severity\":\"minor\",\"title\":\"Return-type changed to cancel function without updating callers/mocks\",\"explanation\":\"scrollTo previously returned void, now returns () => void. Existing mocks like components/table/__tests__/Table.pagination.test.tsx use mockReturnValue(null) assuming void; with strict types this leaks null into scrollRequestId.current and optional chaining hides errors. More importantly external users of scrollTo that ignored return now silently hold no way to clean up, and types/docs not updated.\"}]}","startedAt":"2026-10-16T08:11:55.460Z","finishedAt":"2026-10-16T08:12:26.154Z"}
{"id":"c3034bad-3497-495d-9400-e8b6d8f9c599","caseId":"ant-design-2b12ef528","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"Fix correctly resets clickedRef for Popconfirm emitEvent path but introduces a new console.error/swallow contract change and leaves the two Modal paths (actionFn(close) and actionFn()) unprotected, so synchronous throws there still leave clickedRef stuck and retry impossible.","findings":[{"file":"components/_util/ActionButton.tsx","line":104,"severity":"major","title":"Only emitEvent branch handles sync throw; Modal branches still deadlock","explanation":"The new try/catch wraps only `if (emitEvent) actionFn(e)`. The `else if (actionFn.length) actionFn(close)` and `else actionFn()` paths (used by Modal ConfirmOkBtn/ConfirmCancelBtn) still call actionFn without try/catch. If they throw synchronously, `clickedRef.current = false` on line 125 is never reached (exception before it) and the `else` branch never resets clickedRef at all, so subsequent clicks are ignored exactly like the original Popconfirm bug. Fix needs try/catch around those calls too."},{"file":"components/_util/ActionButton.tsx","line":111,"severity":"major","title":"Sync error is swallowed and console.error changes public API contract","explanation":"Previously a synchronous throw escaped onClick (visible to ErrorBoundaries/window.onerror). Now it is caught, `clickedRef` reset, then swallowed with `console.error('Error in actionFn:', error)` unless isSilent. Popconfirm never passes isSilent so it always logs. This pollutes production consoles, mismatches async path which does `return Promise.reject(e)` unless silent, and hides errors from callers expecting propagation. Should reset clickedRef then re-throw or respect existing isSilent semantics without introducing unconditional console.error."},{"file":"components/popconfirm/__tests__/index.test.tsx","line":453,"severity":"minor","title":"\"Silent mode\" test does not actually exercise silent mode","explanation":"Test titled 'should not log error when onConfirm throws error in silent mode' renders Popconfirm without providing isSilent (ActionButton uses undefined => !isSilent?.() is true) and then asserts consoleError was called, opposite of title. It gives false confidence that silent suppression works and masks that Popconfirm will always log."}]},"text":"This fix targets a stuck confirm button — I'll examine the handler logic to check for regressions.{\"verdict\":\"request_changes\",\"summary\":\"Fix correctly resets clickedRef for Popconfirm emitEvent path but introduces a new console.error/swallow contract change and leaves the two Modal paths (actionFn(close) and actionFn()) unprotected, so synchronous throws there still leave clickedRef stuck and retry impossible.\",\"findings\":[{\"file\":\"components/_util/ActionButton.tsx\",\"line\":104,\"severity\":\"major\",\"title\":\"Only emitEvent branch handles sync throw; Modal branches still deadlock\",\"explanation\":\"The new try/catch wraps only `if (emitEvent) actionFn(e)`. The `else if (actionFn.length) actionFn(close)` and `else actionFn()` paths (used by Modal ConfirmOkBtn/ConfirmCancelBtn) still call actionFn without try/catch. If they throw synchronously, `clickedRef.current = false` on line 125 is never reached (exception before it) and the `else` branch never resets clickedRef at all, so subsequent clicks are ignored exactly like the original Popconfirm bug. Fix needs try/catch around those calls too.\"},{\"file\":\"components/_util/ActionButton.tsx\",\"line\":111,\"severity\":\"major\",\"title\":\"Sync error is swallowed and console.error changes public API contract\",\"explanation\":\"Previously a synchronous throw escaped onClick (visible to ErrorBoundaries/window.onerror). Now it is caught, `clickedRef` reset, then swallowed with `console.error('Error in actionFn:', error)` unless isSilent. Popconfirm never passes isSilent so it always logs. This pollutes production consoles, mismatches async path which does `return Promise.reject(e)` unless silent, and hides errors from callers expecting propagation. Should reset clickedRef then re-throw or respect existing isSilent semantics without introducing unconditional console.error.\"},{\"file\":\"components/popconfirm/__tests__/index.test.tsx\",\"line\":453,\"severity\":\"minor\",\"title\":\"\\\"Silent mode\\\" test does not actually exercise silent mode\",\"explanation\":\"Test titled 'should not log error when onConfirm throws error in silent mode' renders Popconfirm without providing isSilent (ActionButton uses undefined => !isSilent?.() is true) and then asserts consoleError was called, opposite of title. It gives false confidence that silent suppression works and masks that Popconfirm will always log.\"}]}","startedAt":"2026-10-16T08:12:26.165Z","finishedAt":"2026-10-16T08:12:53.861Z"}
{"id":"9ebc75e8-fcdb-4d10-a9cb-156be743667f","caseId":"mantine-ddde2b0ce","kind":"bug","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"PR intentionally renames public APIs (`modal`→`modalKey`, `closeAll`→`closeAllModals` and `updateContextModal` now requires `modalKey`) but provides no backward-compatibility shim; hook consumers (`useModals().closeAll`, `openContextModal('key', props)` / `{modal:'key'}`) will crash/emit `undefined ctx` and docs remain stale.","findings":[{"file":"packages/@mantine/modals/src/context.ts","line":35,"severity":"major","title":"Breaking `openContextModal` signature without alias/fallback","explanation":"Old API was `openContextModal(modal: string, props)` / `{modal: TKey}`. New type is `({modalKey:TKey} & OpenContextModal<TKey>)`. Existing callers passing `modal` prop get `ctx=undefined` in ModalsProvider, causing `modals[undefined]` lookup and crash when rendering ContextModal. No runtime fallback for legacy `modal` field is provided."},{"file":"packages/@mantine/modals/src/context.ts","line":40,"severity":"major","title":"Hook `closeAll` renamed to `closeAllModals` breaks hook consumers","explanation":"Previously `ModalsContextProps` exposed `closeAll`. PR renames to `closeAllModals` and ModalsProvider only provides `closeAllModals`. `const m=useModals(); m.closeAll()` now is undefined → TypeError. Static `modals.closeAll` alias is kept in `events.ts:47` but hook alias is missing. Docs/demos `modals.closeAll` via hook are broken; demos using static import still work."},{"file":"packages/@mantine/modals/src/context.ts","line":42,"severity":"major","title":"`updateContextModal` now requires `modalKey`, old `{modalId}` call no longer types/runs","explanation":"Old signature ` {modalId:string}&Partial<OpenContextModal>` allowed updating by id only (common after `openContextModal` returns id). New signature `{modalKey:TKey}&Partial<...>`. Callers with only `modalId` get TS error and runtime would Destructure `modalKey=undefined`, fallback lookup fails (`find ctx===undefined`) and UPDATE dispatched with wrong id. Provider impl at line 154 makes `modalKey` mandatory contrary to previous ergonomic flow."},{"file":"apps/mantine.dev/src/pages/x/modals.mdx","line":144,"severity":"minor","title":"Docs still document old API (`modal` prop, 2-arg form, `closeAll`)","explanation":"Lines 99-101 and 144-153 still show `openContextModal({modal:'demonstration'})` / 2-arg description and `closeAll` usage. After rename to `modalKey`/`closeAllModals` docs are inconsistent, will confuse migrated users."}]},"text":"Reviewing the breaking API renames for regressions — checking type and runtime compatibility.Spotted potential breakage points — now verifying if older callers were left behind.{\"verdict\":\"request_changes\",\"summary\":\"PR intentionally renames public APIs (`modal`→`modalKey`, `closeAll`→`closeAllModals` and `updateContextModal` now requires `modalKey`) but provides no backward-compatibility shim; hook consumers (`useModals().closeAll`, `openContextModal('key', props)` / `{modal:'key'}`) will crash/emit `undefined ctx` and docs remain stale.\",\"findings\":[{\"file\":\"packages/@mantine/modals/src/context.ts\",\"line\":35,\"severity\":\"major\",\"title\":\"Breaking `openContextModal` signature without alias/fallback\",\"explanation\":\"Old API was `openContextModal(modal: string, props)` / `{modal: TKey}`. New type is `({modalKey:TKey} & OpenContextModal<TKey>)`. Existing callers passing `modal` prop get `ctx=undefined` in ModalsProvider, causing `modals[undefined]` lookup and crash when rendering ContextModal. No runtime fallback for legacy `modal` field is provided.\"},{\"file\":\"packages/@mantine/modals/src/context.ts\",\"line\":40,\"severity\":\"major\",\"title\":\"Hook `closeAll` renamed to `closeAllModals` breaks hook consumers\",\"explanation\":\"Previously `ModalsContextProps` exposed `closeAll`. PR renames to `closeAllModals` and ModalsProvider only provides `closeAllModals`. `const m=useModals(); m.closeAll()` now is undefined → TypeError. Static `modals.closeAll` alias is kept in `events.ts:47` but hook alias is missing. Docs/demos `modals.closeAll` via hook are broken; demos using static import still work.\"},{\"file\":\"packages/@mantine/modals/src/context.ts\",\"line\":42,\"severity\":\"major\",\"title\":\"`updateContextModal` now requires `modalKey`, old `{modalId}` call no longer types/runs\",\"explanation\":\"Old signature ` {modalId:string}&Partial<OpenContextModal>` allowed updating by id only (common after `openContextModal` returns id). New signature `{modalKey:TKey}&Partial<...>`. Callers with only `modalId` get TS error and runtime would Destructure `modalKey=undefined`, fallback lookup fails (`find ctx===undefined`) and UPDATE dispatched with wrong id. Provider impl at line 154 makes `modalKey` mandatory contrary to previous ergonomic flow.\"},{\"file\":\"apps/mantine.dev/src/pages/x/modals.mdx\",\"line\":144,\"severity\":\"minor\",\"title\":\"Docs still document old API (`modal` prop, 2-arg form, `closeAll`)\",\"explanation\":\"Lines 99-101 and 144-153 still show `openContextModal({modal:'demonstration'})` / 2-arg description and `closeAll` usage. After rename to `modalKey`/`closeAllModals` docs are inconsistent, will confuse migrated users.\"}]}","startedAt":"2026-10-16T08:12:53.873Z","finishedAt":"2026-10-16T08:13:44.513Z"}
{"id":"582344e9-7536-4962-bcf6-ac1a55802ea4","caseId":"django-8c8b833d3","kind":"control","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"Pickle round-trip is implemented by storing `module_path` and re-importing, but `_reconstruct` validates \"points to a Task instance\" only via catching `AttributeError` on `.func`, which can be bypassed by any importable object that happens to have a `.func` attribute pointing to an allowed function.","findings":[{"file":"django/tasks/base.py","line":58,"severity":"major","title":"_reconstruct does not verify Task type","explanation":"`_reconstruct` does `func = import_string(func_path); kwargs['func'] = func.func` inside try/except ImportError,AttributeError. If `func_path` imports an object that is not a Task but has a `.func` attribute (many decorators/wrappers, or attacker-controlled module), `func.func` succeeds and an arbitrary callable is wrapped as Task. The subsequent `validate_task` only checks `is_module_level_function` (e.g. subprocess.run is considered valid), so an attacker-controlled pickle payload could synthesize a Task for a non-task function. Should explicitly check `isinstance(func, Task)` (or `type(func) is Task`/subclass) and raise ValueError otherwise."},{"file":"django/tasks/base.py","line":58,"severity":"minor","title":"_reconstruct mutates caller dict and leaks KeyError","explanation":"`kwargs['func'] = func.func` mutates the dict passed to `__reduce__`/tests. Also `kwargs['func']` lookup raises raw KeyError if 'func' missing from crafted pickle, instead of the intended ValueError(\"Expected ... to point to a Task instance.\"). Copy the dict before mutating and validate key presence."},{"file":"django/tasks/base.py","line":69,"severity":"nit","title":"TaskResult pickling relies on transitive dataclass pickle","explanation":"Docs claim TaskResult is picklable but no __reduce__/__getstate__ is added. It works only because Task is now picklable and TaskResult is a frozen-slots dataclass whose auto pickle bypasses __post_init__ normalize_json. This is fragile and test `test_pickle_task_result` only covers READY result with _return_value=None; a SUCCESSFUL/FAILED result with mutated attributes (_return_value, enqueued_at via object.__setattr__) should have an explicit test to ensure full state round-trips."}]},"text":"Pickling Task is now on the table — checking the implementation for regressions.Initial scan shows the approach is plausible; now I’ll probe the implementation for edge cases and safety.{\"verdict\":\"request_changes\",\"summary\":\"Pickle round-trip is implemented by storing `module_path` and re-importing, but `_reconstruct` validates \\\"points to a Task instance\\\" only via catching `AttributeError` on `.func`, which can be bypassed by any importable object that happens to have a `.func` attribute pointing to an allowed function.\",\"findings\":[{\"file\":\"django/tasks/base.py\",\"line\":58,\"severity\":\"major\",\"title\":\"_reconstruct does not verify Task type\",\"explanation\":\"`_reconstruct` does `func = import_string(func_path); kwargs['func'] = func.func` inside try/except ImportError,AttributeError. If `func_path` imports an object that is not a Task but has a `.func` attribute (many decorators/wrappers, or attacker-controlled module), `func.func` succeeds and an arbitrary callable is wrapped as Task. The subsequent `validate_task` only checks `is_module_level_function` (e.g. subprocess.run is considered valid), so an attacker-controlled pickle payload could synthesize a Task for a non-task function. Should explicitly check `isinstance(func, Task)` (or `type(func) is Task`/subclass) and raise ValueError otherwise.\"},{\"file\":\"django/tasks/base.py\",\"line\":58,\"severity\":\"minor\",\"title\":\"_reconstruct mutates caller dict and leaks KeyError\",\"explanation\":\"`kwargs['func'] = func.func` mutates the dict passed to `__reduce__`/tests. Also `kwargs['func']` lookup raises raw KeyError if 'func' missing from crafted pickle, instead of the intended ValueError(\\\"Expected ... to point to a Task instance.\\\"). Copy the dict before mutating and validate key presence.\"},{\"file\":\"django/tasks/base.py\",\"line\":69,\"severity\":\"nit\",\"title\":\"TaskResult pickling relies on transitive dataclass pickle\",\"explanation\":\"Docs claim TaskResult is picklable but no __reduce__/__getstate__ is added. It works only because Task is now picklable and TaskResult is a frozen-slots dataclass whose auto pickle bypasses __post_init__ normalize_json. This is fragile and test `test_pickle_task_result` only covers READY result with _return_value=None; a SUCCESSFUL/FAILED result with mutated attributes (_return_value, enqueued_at via object.__setattr__) should have an explicit test to ensure full state round-trips.\"}]}","startedAt":"2026-10-16T08:13:44.525Z","finishedAt":"2026-10-16T08:14:45.277Z"}
{"id":"4e64b1ce-10df-4dd2-ba69-247220fe55d8","caseId":"core-6a61f4452","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Fix correctly addresses suspense+disabled-teleport race: using live parentNode avoids mounting into stale hiddenContainer and suppressing child moves while pending avoids crashing on un-mounted children.","findings":[]},"text":"Reviewing this teleport fix — inspecting the pending-mount guard against Suspense transitions.I’ve seen the two main fixes — now I’ll dig into how Suspense shuffles containers to verify the approach.{\"verdict\":\"approve\",\"summary\":\"Fix correctly addresses suspense+disabled-teleport race: using live parentNode avoids mounting into stale hiddenContainer and suppressing child moves while pending avoids crashing on un-mounted children.\",\"findings\":[]}","startedAt":"2026-10-16T08:14:45.289Z","finishedAt":"2026-10-16T08:15:27.291Z"}
{"id":"9919b7d6-d4ad-40d7-a1fe-e0156bcbe2aa","caseId":"svelte-a9d8439ad","kind":"control","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"Fix correctly addresses stale-branch case but introduces an always-true filter, unbounded retention of created effects, and rescheduling into later batches.","findings":[{"file":"packages/svelte/src/internal/client/reactivity/batch.js","line":543,"severity":"major","title":"current_unequal filter compares value to source identity","explanation":"`[...batch.current.keys()].filter(c=> this.current.has(c) ? this.current.get(c)[0] !== c : true)` compares the stored batch value to the source object `c` itself, so `!== c` is almost always true. Intended check is whether `this` and `batch` disagree on value, e.g. `this.current.get(c)[0] !== batch.current.get(c)[0]`. As written the filter is a no-op and causes over-scheduling into every batch."},{"file":"packages/svelte/src/internal/client/reactivity/batch.js","line":152,"severity":"major","title":"#new_effects never cleared — memory leak and duplicate rescheduling","explanation":"`#new_effects` is appended to in `register_created_effect()` but never cleared after `#commit()` (and `discard()` does not clear it). The committing Batch retains references to all effects it created forever, and if the same Batch participates in multiple commit traversals the same effects are re-evaluated repeatedly."},{"file":"packages/svelte/src/internal/client/reactivity/batch.js","line":546,"severity":"major","title":"Reschedules new effects into later batches, not just prior batches","explanation":"`#commit()` loops over all `batches` but the new rescheduling block does not check `is_earlier` (batch.id < this.id). Description says to reschedule in prior batches only; applying to `is_earlier===false` pollutes newer/future batches with effects from an older commit and can cause duplicate/incorrect renders."}]},"text":"Reviewing this batch rescheduling fix — checking the new effect tracking for leaks and regressions.{\"verdict\":\"request_changes\",\"summary\":\"Fix correctly addresses stale-branch case but introduces an always-true filter, unbounded retention of created effects, and rescheduling into later batches.\",\"findings\":[{\"file\":\"packages/svelte/src/internal/client/reactivity/batch.js\",\"line\":543,\"severity\":\"major\",\"title\":\"current_unequal filter compares value to source identity\",\"explanation\":\"`[...batch.current.keys()].filter(c=> this.current.has(c) ? this.current.get(c)[0] !== c : true)` compares the stored batch value to the source object `c` itself, so `!== c` is almost always true. Intended check is whether `this` and `batch` disagree on value, e.g. `this.current.get(c)[0] !== batch.current.get(c)[0]`. As written the filter is a no-op and causes over-scheduling into every batch.\"},{\"file\":\"packages/svelte/src/internal/client/reactivity/batch.js\",\"line\":152,\"severity\":\"major\",\"title\":\"#new_effects never cleared — memory leak and duplicate rescheduling\",\"explanation\":\"`#new_effects` is appended to in `register_created_effect()` but never cleared after `#commit()` (and `discard()` does not clear it). The committing Batch retains references to all effects it created forever, and if the same Batch participates in multiple commit traversals the same effects are re-evaluated repeatedly.\"},{\"file\":\"packages/svelte/src/internal/client/reactivity/batch.js\",\"line\":546,\"severity\":\"major\",\"title\":\"Reschedules new effects into later batches, not just prior batches\",\"explanation\":\"`#commit()` loops over all `batches` but the new rescheduling block does not check `is_earlier` (batch.id < this.id). Description says to reschedule in prior batches only; applying to `is_earlier===false` pollutes newer/future batches with effects from an older commit and can cause duplicate/incorrect renders.\"}]}","startedAt":"2026-10-16T08:15:27.304Z","finishedAt":"2026-10-16T08:16:09.297Z"}
{"id":"24f31872-8b3a-4984-aa03-e0ec8b9323de","caseId":"tldraw-79c550bd6","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Fix correctly offsets the cross marker by half-diagonal (×√2), replaces ad-hoc dash sizing with getPerfectDashProps, measures arc stubs along the circle, and falls back to solid below 20% zoom; no crash or API break observed.","findings":[{"file":"packages/tldraw/src/lib/overlays/ArrowBindingHintOverlayUtil.ts","line":30,"severity":"minor","title":"options.dashLengthRatio now unused","explanation":"PR keeps dashLengthRatio:2.5 in default options but the new draw path ignores it, calling getPerfectDashProps with its default lengthRatio=2. Dash sizing therefore silently changed; either wire dashLengthRatio through or remove the dead option."},{"file":"packages/tldraw/src/lib/overlays/ArrowBindingHintOverlayUtil.ts","line":177,"severity":"minor","title":"Arc stub pathLength can become zero/NaN","explanation":"pathLength = max(0, radius*|sweep|-markerRadius) can be 0 when trim exceeds the short sweep (e.g. tight radius or straight-line dist check passed but arc length shorter). getPerfectDashProps(0,…) yields dashLength 0 → division-by-zero producing Infinity/NaN dash arrays. Harmless due to early dist>markerRadius guard but leaves ctx.setLineDash([NaN,NaN]) possible; clamp to a small epsilon or fallback to solid."},{"file":"packages/tldraw/src/lib/overlays/ArrowBindingHintOverlayUtil.ts","line":155,"severity":"nit","title":"Maximal corner radius over-trims for some tangent directions","explanation":"Cross footprint is two zero-width segments; distance to the X along arbitrary tangent varies between 0 and half*√2. Using the maximal corner distance guarantees no overlap but makes the gap angle-dependent; spec says this is intentional visual parity with dot, so not a defect."}]},"text":"Fixing the cross-corner stub overlap — I'll inspect the new offset and arc math for regressions.I've tracked the marker scaling and arc-length fixes — now I'll sample a few more edge cases before finishing up.The core rendering path checks out — now I'm doing a final review of the configuration changes.{\"verdict\":\"approve\",\"summary\":\"Fix correctly offsets the cross marker by half-diagonal (×√2), replaces ad-hoc dash sizing with getPerfectDashProps, measures arc stubs along the circle, and falls back to solid below 20% zoom; no crash or API break observed.\",\"findings\":[{\"file\":\"packages/tldraw/src/lib/overlays/ArrowBindingHintOverlayUtil.ts\",\"line\":30,\"severity\":\"minor\",\"title\":\"options.dashLengthRatio now unused\",\"explanation\":\"PR keeps dashLengthRatio:2.5 in default options but the new draw path ignores it, calling getPerfectDashProps with its default lengthRatio=2. Dash sizing therefore silently changed; either wire dashLengthRatio through or remove the dead option.\"},{\"file\":\"packages/tldraw/src/lib/overlays/ArrowBindingHintOverlayUtil.ts\",\"line\":177,\"severity\":\"minor\",\"title\":\"Arc stub pathLength can become zero/NaN\",\"explanation\":\"pathLength = max(0, radius*|sweep|-markerRadius) can be 0 when trim exceeds the short sweep (e.g. tight radius or straight-line dist check passed but arc length shorter). getPerfectDashProps(0,…) yields dashLength 0 → division-by-zero producing Infinity/NaN dash arrays. Harmless due to early dist>markerRadius guard but leaves ctx.setLineDash([NaN,NaN]) possible; clamp to a small epsilon or fallback to solid.\"},{\"file\":\"packages/tldraw/src/lib/overlays/ArrowBindingHintOverlayUtil.ts\",\"line\":155,\"severity\":\"nit\",\"title\":\"Maximal corner radius over-trims for some tangent directions\",\"explanation\":\"Cross footprint is two zero-width segments; distance to the X along arbitrary tangent varies between 0 and half*√2. Using the maximal corner distance guarantees no overlap but makes the gap angle-dependent; spec says this is intentional visual parity with dot, so not a defect.\"}]}","startedAt":"2026-10-16T08:16:09.309Z","finishedAt":"2026-10-16T08:17:38.791Z"}
{"id":"f7722b75-3ca2-4188-8832-33f037280157","caseId":"prettier-3031cd3b8","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Removes incorrect lifting of consequent/alternate dangling comments that caused duplicates for empty container branches (`[]`/`{}` with interior comments). Dangling is now handled only by the branch node's own printer and by the ternary's `node`/`test` dangling handling, matching expected snapshot.","findings":[]},"text":"Fix looks narrow — now checking whether the comment cleanup deletes real content.I've traced the duplicate output path — now digging through related cases to verify the scope is complete.{\"verdict\":\"approve\",\"summary\":\"Removes incorrect lifting of consequent/alternate dangling comments that caused duplicates for empty container branches (`[]`/`{}` with interior comments). Dangling is now handled only by the branch node's own printer and by the ternary's `node`/`test` dangling handling, matching expected snapshot.\",\"findings\":[]}","startedAt":"2026-10-16T08:17:38.804Z","finishedAt":"2026-10-16T08:18:41.713Z"}
{"id":"7fb35693-9474-4575-9cd6-fe9001073866","caseId":"tiptap-d2ad165b7","kind":"control","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"Fix correctly syncs colgroup for non-resizable tables but introduces a snapshot-based `isEditable` guard and an unconditional wrapper that regresses existing behavior.","findings":[{"file":"packages/extension-table/src/table/table.ts","line":517,"severity":"major","title":"NodeView choice frozen on `isEditable` snapshot breaks toggle and SSR","explanation":"`addNodeView()` and `addProseMirrorPlugins()` compute `isResizable = options.resizable && editor.isEditable` once at creation. Existing NodeViews are not recreated on `setEditable(false/true)` (Editor#setEditable only calls view.updateState). A table created `resizable:true, editable:true` keeps the plugin view after becoming read-only; a table created `resizable:true, editable:false` keeps `TableView` after becoming editable and never gains resize handles. The guard should not depend on a snapshot - either gate only on `options.resizable` and always include columnResizing plugin (plugin itself disables interaction when not editable) or make the factory evaluate current editability per-node."},{"file":"packages/extension-table/src/table/TableView.ts","line":89,"severity":"major","title":"Non-resizable tables now always gain `div.tableWrapper` breaking renderWrapper:false","explanation":"Previously without a NodeView non-resizable tables rendered as `<table>…</table>` (wrapper only if `renderWrapper:true`). `TableView` unconditionally creates `dom=div.tableWrapper > table`. Existing selectors/styles assuming table is direct child of `.tiptap` break, and serialized HTML via `renderHTML` (which respects `renderWrapper`) diverges from view DOM. Fix needs a lightweight NodeView that uses `table` as `dom`/`contentDOM=tbody` or respects `renderWrapper`."},{"file":"packages/extension-table/src/table/TableView.ts","line":86,"severity":"minor","title":"User style on table node not kept in sync on update","explanation":"Constructor copies `node.attrs.style` to `table.style.cssText` but `update()` only calls `updateColumns()`. Programmatic attribute changes (e.g. setting width style) leave stale inline styles; renderHTML path recomputes style each transaction."},{"file":"packages/extension-table/src/table/table.ts","line":524,"severity":"nit","title":"Extra `view` arg ignored and decorations/getPos dropped","explanation":"Factory `({node,view})=>new View(node,cellMinWidth,view)` passes `view` to `TableView` whose constructor signature is `(node,cellMinWidth)` and drops `getPos/decorations`. Unused now but inconsistent and prevents future custom Views needing those args."}]},"text":"Reviewing the non-resizable colgroup fix — checking how the new NodeView interacts with resizable tables.I spotted some potential interaction issues — now I'll dig deeper into the underlying implementation.{\"verdict\":\"request_changes\",\"summary\":\"Fix correctly syncs colgroup for non-resizable tables but introduces a snapshot-based `isEditable` guard and an unconditional wrapper that regresses existing behavior.\",\"findings\":[{\"file\":\"packages/extension-table/src/table/table.ts\",\"line\":517,\"severity\":\"major\",\"title\":\"NodeView choice frozen on `isEditable` snapshot breaks toggle and SSR\",\"explanation\":\"`addNodeView()` and `addProseMirrorPlugins()` compute `isResizable = options.resizable && editor.isEditable` once at creation. Existing NodeViews are not recreated on `setEditable(false/true)` (Editor#setEditable only calls view.updateState). A table created `resizable:true, editable:true` keeps the plugin view after becoming read-only; a table created `resizable:true, editable:false` keeps `TableView` after becoming editable and never gains resize handles. The guard should not depend on a snapshot - either gate only on `options.resizable` and always include columnResizing plugin (plugin itself disables interaction when not editable) or make the factory evaluate current editability per-node.\"},{\"file\":\"packages/extension-table/src/table/TableView.ts\",\"line\":89,\"severity\":\"major\",\"title\":\"Non-resizable tables now always gain `div.tableWrapper` breaking renderWrapper:false\",\"explanation\":\"Previously without a NodeView non-resizable tables rendered as `<table>…</table>` (wrapper only if `renderWrapper:true`). `TableView` unconditionally creates `dom=div.tableWrapper > table`. Existing selectors/styles assuming table is direct child of `.tiptap` break, and serialized HTML via `renderHTML` (which respects `renderWrapper`) diverges from view DOM. Fix needs a lightweight NodeView that uses `table` as `dom`/`contentDOM=tbody` or respects `renderWrapper`.\"},{\"file\":\"packages/extension-table/src/table/TableView.ts\",\"line\":86,\"severity\":\"minor\",\"title\":\"User style on table node not kept in sync on update\",\"explanation\":\"Constructor copies `node.attrs.style` to `table.style.cssText` but `update()` only calls `updateColumns()`. Programmatic attribute changes (e.g. setting width style) leave stale inline styles; renderHTML path recomputes style each transaction.\"},{\"file\":\"packages/extension-table/src/table/table.ts\",\"line\":524,\"severity\":\"nit\",\"title\":\"Extra `view` arg ignored and decorations/getPos dropped\",\"explanation\":\"Factory `({node,view})=>new View(node,cellMinWidth,view)` passes `view` to `TableView` whose constructor signature is `(node,cellMinWidth)` and drops `getPos/decorations`. Unused now but inconsistent and prevents future custom Views needing those args.\"}]}","startedAt":"2026-10-16T08:18:41.727Z","finishedAt":"2026-10-16T08:19:45.134Z"}
{"id":"801c5089-6029-4dc0-bacd-10cb10190adc","caseId":"django-bb7a7701b","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Fix correctly collapses multi-column ForeignObject targets into a single ColPairs expression for values()/values_list(), aligning add_fields with existing composite-field handling in build_filter and SQL compilation/deserialization. Logic for converters, alias_cols and tuple reconstruction is consistent.","findings":[]},"text":"Reviewing the fix for multi-column ForeignObject values — inspecting query construction and related tests.I've mapped the core query logic — now I'll verify how this affects values selections and check for edge cases.I've traced how value selections handle multi-column references — now I'll run final verification checks.{\"verdict\":\"approve\",\"summary\":\"Fix correctly collapses multi-column ForeignObject targets into a single ColPairs expression for values()/values_list(), aligning add_fields with existing composite-field handling in build_filter and SQL compilation/deserialization. Logic for converters, alias_cols and tuple reconstruction is consistent.\",\"findings\":[]}","startedAt":"2026-10-16T10:04:51.628Z","finishedAt":"2026-10-16T10:06:38.875Z"}
{"id":"fb09d8f4-066d-41d8-b342-ff988ebd97f1","caseId":"svelte-8895bd44f","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Fix correctly coalesces unknown / possibly-nullish title value to \"\" via `?? ''`, matching text-node semantics and server escaping. No regression observed; single-value known-nullish already normalized upstream.","findings":[{"file":"packages/svelte/src/compiler/phases/3-transform/client/visitors/TitleElement.js","line":21,"severity":"minor","title":"Known-nullish branch does not coerce to empty string","explanation":"Line 22 uses `b.literal(evaluated.value)` verbatim. If `evaluated.value` were `null`/`undefined` it would emit `document.title = null`, showing \"null\"/\"undefined\" instead of \"\". Currently unreachable because `build_template_chunk` normalizes single-expression and multi-part known nullish to `(value ?? '')+''`, but diverges from `shared/utils.js:115,134` which use `?? ''` and leaves a latent inconsistency if normalization changes."}]},"text":"Spotting the nullish title edge case — I'll verify the compiler's handling holds up under scrutiny.I've pinpointed how the title interacts with reactive state — now examining the underlying evaluation rules to check for consistent coercion.I've mapped out the title coercion scenarios — now I'm checking for any surrounding code that might affect the fix's completeness.{\"verdict\":\"approve\",\"summary\":\"Fix correctly coalesces unknown / possibly-nullish title value to \\\"\\\" via `?? ''`, matching text-node semantics and server escaping. No regression observed; single-value known-nullish already normalized upstream.\",\"findings\":[{\"file\":\"packages/svelte/src/compiler/phases/3-transform/client/visitors/TitleElement.js\",\"line\":21,\"severity\":\"minor\",\"title\":\"Known-nullish branch does not coerce to empty string\",\"explanation\":\"Line 22 uses `b.literal(evaluated.value)` verbatim. If `evaluated.value` were `null`/`undefined` it would emit `document.title = null`, showing \\\"null\\\"/\\\"undefined\\\" instead of \\\"\\\". Currently unreachable because `build_template_chunk` normalizes single-expression and multi-part known nullish to `(value ?? '')+''`, but diverges from `shared/utils.js:115,134` which use `?? ''` and leaves a latent inconsistency if normalization changes.\"}]}","startedAt":"2026-10-16T10:06:38.887Z","finishedAt":"2026-10-16T10:07:53.558Z"}
{"id":"0b9134ac-defc-4799-8348-7d2993727541","caseId":"ant-design-2037fa783","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"PR correctly adds missing `rangePlaceholder` entries to 30 TimePicker locale files; additions are additive, match type `TimePickerLocale`, and complete coverage for all existing time-picker locales without breaking API.","findings":[]},"text":"Patch adds range placeholders – I'll cross-check the type contract and existing translations.Coverage looks complete — now I'm sampling the patched files and the broader locale map for gaps.{\"verdict\":\"approve\",\"summary\":\"PR correctly adds missing `rangePlaceholder` entries to 30 TimePicker locale files; additions are additive, match type `TimePickerLocale`, and complete coverage for all existing time-picker locales without breaking API.\",\"findings\":[]}","startedAt":"2026-10-16T10:08:01.955Z","finishedAt":"2026-10-16T10:08:50.142Z"}
{"id":"774ab087-820f-48c6-b287-49f6bc8e712f","caseId":"ant-design-11370a274","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Conditionally hiding the empty `.ant-card-body` when `children` is absent (except during `loading`) correctly removes unwanted padding for cover/actions/title-only cards while preserving `0`/string children and loading skeleton behavior via `toArray` filtering.","findings":[{"file":"components/card/Card.tsx","line":263,"severity":"minor","title":"Semantic body customization lost when body is suppressed","explanation":"Merged `classNames.body`/`styles.body` and `bodyStyle` are only applied when `body` is rendered. A card with no `children` but with custom body styles/classNames will now render no element for them to attach to. Previous code always rendered the div so styling applied even when empty. Low severity but could surprise users relying on body styles for empty cards."},{"file":"components/card/style/index.ts","line":343,"severity":"minor","title":"Missing bottom-radius handling when body is omitted","explanation":"`.ant-card-body` previously always provided bottom `border-radius: 0 0 LG LG` (and `:first-child` handling). When `body` is now `null`, header-only or cover-only cards end with head/cover which only have top radius, leaving the container’s bottom corners unfilled. Visual regression for `<Card title=\"x\"/>` or similar empty-content cards – bottom corners stay square."}]},"text":"Reviewing your Card fix — checking whether skipping the empty body is safe for existing usage.Core logic checks out — now digging into styling, edge cases and API impacts.{\"verdict\":\"approve\",\"summary\":\"Conditionally hiding the empty `.ant-card-body` when `children` is absent (except during `loading`) correctly removes unwanted padding for cover/actions/title-only cards while preserving `0`/string children and loading skeleton behavior via `toArray` filtering.\",\"findings\":[{\"file\":\"components/card/Card.tsx\",\"line\":263,\"severity\":\"minor\",\"title\":\"Semantic body customization lost when body is suppressed\",\"explanation\":\"Merged `classNames.body`/`styles.body` and `bodyStyle` are only applied when `body` is rendered. A card with no `children` but with custom body styles/classNames will now render no element for them to attach to. Previous code always rendered the div so styling applied even when empty. Low severity but could surprise users relying on body styles for empty cards.\"},{\"file\":\"components/card/style/index.ts\",\"line\":343,\"severity\":\"minor\",\"title\":\"Missing bottom-radius handling when body is omitted\",\"explanation\":\"`.ant-card-body` previously always provided bottom `border-radius: 0 0 LG LG` (and `:first-child` handling). When `body` is now `null`, header-only or cover-only cards end with head/cover which only have top radius, leaving the container’s bottom corners unfilled. Visual regression for `<Card title=\\\"x\\\"/>` or similar empty-content cards – bottom corners stay square.\"}]}","startedAt":"2026-10-16T10:08:50.151Z","finishedAt":"2026-10-16T10:09:31.405Z"}
{"id":"4a36c1b0-c94f-4b2c-ba51-c73cf7eb7094","caseId":"ant-design-3c297da5e","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Fix correctly propagates List CSS-var tokens inside <Spin> by introducing `-container` element and `extraCssVarPrefixCls`; snapshots confirm vars now appear on `ul`/grid `Row`. No breaking API change.","findings":[{"file":"components/list/index.tsx","line":279,"severity":"minor","title":"Grid path duplicates cssVar class","explanation":"Row receives List's cssVarCls via className and internally injects its own cssVarCls (same value in cssVar mode). Result snapshot shows `class=\"ant-row ant-list-container css-var-test-id css-var-test-id\"` with duplicate token. Duplicates are harmless but wasteful and indicate redundant injection; dedup/conditional merge would be cleaner."},{"file":"components/list/index.tsx","line":289,"severity":"minor","title":"Empty / manual-children not covered by container","explanation":"Only dataSource→ul/Row paths get `ant-list-container`+cssVarCls. When dataSource empty (emptyText) or when List is used with direct <List.Item> children, the rendered nodes stay inside <Spin>-container without the extra host, so legacy component tokens (e.g. theme.components.List.colorText) still won't propagate there. Intent is preserved for main path but coverage is incomplete."},{"file":"components/list/style/index.ts","line":458,"severity":"nit","title":"Relies on upgraded cssinjs feature","explanation":"extraCssVarPrefixCls requires @ant-design/cssinjs ^2.1 / cssinjs-utils ^2.1 (upgraded in package.json). Consumers pinned to 2.0 will ignore the option and get the extra DOM class without vars; not a crash but worth noting in release notes."}]},"text":"Reviewing the List token propagation change — checking how the new container class interacts with Spin and Grid.I've spotted a potential rendering inefficiency in the grid layout — now I'll verify whether this pattern matches the codebase's usual token handling.{\"verdict\":\"approve\",\"summary\":\"Fix correctly propagates List CSS-var tokens inside <Spin> by introducing `-container` element and `extraCssVarPrefixCls`; snapshots confirm vars now appear on `ul`/grid `Row`. No breaking API change.\",\"findings\":[{\"file\":\"components/list/index.tsx\",\"line\":279,\"severity\":\"minor\",\"title\":\"Grid path duplicates cssVar class\",\"explanation\":\"Row receives List's cssVarCls via className and internally injects its own cssVarCls (same value in cssVar mode). Result snapshot shows `class=\\\"ant-row ant-list-container css-var-test-id css-var-test-id\\\"` with duplicate token. Duplicates are harmless but wasteful and indicate redundant injection; dedup/conditional merge would be cleaner.\"},{\"file\":\"components/list/index.tsx\",\"line\":289,\"severity\":\"minor\",\"title\":\"Empty / manual-children not covered by container\",\"explanation\":\"Only dataSource→ul/Row paths get `ant-list-container`+cssVarCls. When dataSource empty (emptyText) or when List is used with direct <List.Item> children, the rendered nodes stay inside <Spin>-container without the extra host, so legacy component tokens (e.g. theme.components.List.colorText) still won't propagate there. Intent is preserved for main path but coverage is incomplete.\"},{\"file\":\"components/list/style/index.ts\",\"line\":458,\"severity\":\"nit\",\"title\":\"Relies on upgraded cssinjs feature\",\"explanation\":\"extraCssVarPrefixCls requires @ant-design/cssinjs ^2.1 / cssinjs-utils ^2.1 (upgraded in package.json). Consumers pinned to 2.0 will ignore the option and get the extra DOM class without vars; not a crash but worth noting in release notes.\"}]}","startedAt":"2026-10-16T10:09:31.421Z","finishedAt":"2026-10-16T10:10:31.912Z"}
{"id":"0ec09d47-21ad-4261-9a58-54b72c1dcbbc","caseId":"django-c1aa4a7a7","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Change correctly moves per-file \"Deleting\" logs from level 1 to level 2 and replaces them with a summary count at verbosity >=1, matching the existing handling for conflicts/posts-processing; dry-run and non-local storage paths are handled parallel to other counters.","findings":[]},"text":"Reviewing the verbosity fix — inspecting how deleted files are now summarized.{\"verdict\":\"approve\",\"summary\":\"Change correctly moves per-file \\\"Deleting\\\" logs from level 1 to level 2 and replaces them with a summary count at verbosity >=1, matching the existing handling for conflicts/posts-processing; dry-run and non-local storage paths are handled parallel to other counters.\",\"findings\":[]}","startedAt":"2026-10-16T10:10:31.921Z","finishedAt":"2026-10-16T10:11:05.443Z"}
{"id":"fdaff844-3015-4f6d-9c21-cac9065af224","caseId":"django-7732f942a","kind":"control","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"Pluggable parser works for per-request case but the property/descriptor design is shadowed by a same-named class attribute – subclass/global assignment `multipart_parser_class = X` clobbers the property and disables the immutability guard, and the exception type is inconsistent with `upload_handlers`.","findings":[{"file":"django/http/request.py","line":368,"severity":"major","title":"Class assignment clobbers property — subclass override disables guard","explanation":"`HttpRequest.multipart_parser_class` is defined as a data-descriptor property backed by `_multipart_parser_class`. Any `class Sub(WSGIRequest): multipart_parser_class = CustomParser` or `HttpRequest.multipart_parser_class = CustomParser` assignment replaces the property object in that class dict (MRO finds the subclass attr first). Instances then get a plain attribute, so `self.multipart_parser_class(META, ...)` still resolves but the setter guard (`hasattr(_files)`) is lost — you can silently set `request.multipart_parser_class = ...` after `request.POST`/`_files` with no error. This is exactly what `tests/requests_tests/tests.py:test_custom_multipart_parser_class` does. Intended subclass customization should override `_multipart_parser_class`, or implementation should use a custom descriptor that delegates to the class default without name collision."},{"file":"django/http/request.py","line":374,"severity":"minor","title":"Inconsistent exception type vs upload_handlers","explanation":"`upload_handlers` setter raises `AttributeError` after parsing, while `multipart_parser_class` setter raises `RuntimeError` for the same state (`hasattr(_files)`). Callers catching `AttributeError` for post-parse mutation will miss the new case; choose one type for consistency (existing API uses `AttributeError`)."},{"file":"docs/ref/request-response.txt","line":218,"severity":"nit","title":"Doc example missing imports","explanation":"Example defines `CustomMultiPartParser.parse()` using `QueryDict` and `MultiValueDict` but only imports `MultiPartParser`. Rendering the snippet as-is raises `NameError`. Add `from django.http import QueryDict` / `from django.utils.datastructures import MultiValueDict` or fully qualify."}]},"text":"Checking the new pluggable parser hook — inspecting the core changes for regressions.I've mapped how the new attribute interacts with WSGI and ASGI requests — now hunting for compatibility issues and design gaps.{\"verdict\":\"request_changes\",\"summary\":\"Pluggable parser works for per-request case but the property/descriptor design is shadowed by a same-named class attribute – subclass/global assignment `multipart_parser_class = X` clobbers the property and disables the immutability guard, and the exception type is inconsistent with `upload_handlers`.\",\"findings\":[{\"file\":\"django/http/request.py\",\"line\":368,\"severity\":\"major\",\"title\":\"Class assignment clobbers property — subclass override disables guard\",\"explanation\":\"`HttpRequest.multipart_parser_class` is defined as a data-descriptor property backed by `_multipart_parser_class`. Any `class Sub(WSGIRequest): multipart_parser_class = CustomParser` or `HttpRequest.multipart_parser_class = CustomParser` assignment replaces the property object in that class dict (MRO finds the subclass attr first). Instances then get a plain attribute, so `self.multipart_parser_class(META, ...)` still resolves but the setter guard (`hasattr(_files)`) is lost — you can silently set `request.multipart_parser_class = ...` after `request.POST`/`_files` with no error. This is exactly what `tests/requests_tests/tests.py:test_custom_multipart_parser_class` does. Intended subclass customization should override `_multipart_parser_class`, or implementation should use a custom descriptor that delegates to the class default without name collision.\"},{\"file\":\"django/http/request.py\",\"line\":374,\"severity\":\"minor\",\"title\":\"Inconsistent exception type vs upload_handlers\",\"explanation\":\"`upload_handlers` setter raises `AttributeError` after parsing, while `multipart_parser_class` setter raises `RuntimeError` for the same state (`hasattr(_files)`). Callers catching `AttributeError` for post-parse mutation will miss the new case; choose one type for consistency (existing API uses `AttributeError`).\"},{\"file\":\"docs/ref/request-response.txt\",\"line\":218,\"severity\":\"nit\",\"title\":\"Doc example missing imports\",\"explanation\":\"Example defines `CustomMultiPartParser.parse()` using `QueryDict` and `MultiValueDict` but only imports `MultiPartParser`. Rendering the snippet as-is raises `NameError`. Add `from django.http import QueryDict` / `from django.utils.datastructures import MultiValueDict` or fully qualify.\"}]}","startedAt":"2026-10-16T10:11:05.460Z","finishedAt":"2026-10-16T10:12:02.491Z"}
{"id":"2854cad1-3986-4126-9ea9-1360cd41cdc5","caseId":"prettier-4d2205926","kind":"control","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"Fix correctly unifies public class / object method comment handling but leaves `ClassPrivateMethod` out of `isFunctionLikeNode`, so Babel-private methods stay as trailing `method() { // comment` while acorn/espree/meriyah/oxc private methods (as `MethodDefinition`) are now moved inside the block, creating a new cross-parser divergent / unstable case.","findings":[{"file":"src/language-js/comments/handle-comments.js","line":565,"severity":"major","title":"ClassPrivateMethod not included in isFunctionLikeNode","explanation":"ESTree private class methods are `MethodDefinition` (with PrivateIdentifier) and are now handled, but Babel private class methods are `ClassPrivateMethod`. Old code treated neither; new code adds `ClassMethod`+`MethodDefinition` but not `ClassPrivateMethod`, so public class methods are fixed while `class A { #foo() // c {} }` stays trailing on Babel but moves inside on acorn/espree/meriyah/oxc. This re-introduces parser-dependent formatting and leaves part of the claimed \"class vs object\" inconsistency unfixed. Add \"ClassPrivateMethod\" (and handle its `body` like ClassMethod) to keep parsers in sync."},{"file":"src/language-js/comments/handle-comments.js","line":701,"severity":"minor","title":"Unsafe access to enclosingNode.value.body","explanation":"For `MethodDefinition` the code does `enclosingNode.value.body` without guarding. Abstract/declare methods (e.g., `TSAbstractMethodDefinition`/`TSDeclareMethod`) can lack `value`/`body`; even with current guard (`followingNode` is BlockStatement) a malformed/missing `value` would throw TypeError instead of falling through. Use `enclosingNode.value?.body`."}]},"text":"Reviewing the class method comment fix — inspecting the new unified logic for edge cases.I see how the comment attachment logic now covers class methods — I'll check whether any method types were left out.{\"verdict\":\"request_changes\",\"summary\":\"Fix correctly unifies public class / object method comment handling but leaves `ClassPrivateMethod` out of `isFunctionLikeNode`, so Babel-private methods stay as trailing `method() { // comment` while acorn/espree/meriyah/oxc private methods (as `MethodDefinition`) are now moved inside the block, creating a new cross-parser divergent / unstable case.\",\"findings\":[{\"file\":\"src/language-js/comments/handle-comments.js\",\"line\":565,\"severity\":\"major\",\"title\":\"ClassPrivateMethod not included in isFunctionLikeNode\",\"explanation\":\"ESTree private class methods are `MethodDefinition` (with PrivateIdentifier) and are now handled, but Babel private class methods are `ClassPrivateMethod`. Old code treated neither; new code adds `ClassMethod`+`MethodDefinition` but not `ClassPrivateMethod`, so public class methods are fixed while `class A { #foo() // c {} }` stays trailing on Babel but moves inside on acorn/espree/meriyah/oxc. This re-introduces parser-dependent formatting and leaves part of the claimed \\\"class vs object\\\" inconsistency unfixed. Add \\\"ClassPrivateMethod\\\" (and handle its `body` like ClassMethod) to keep parsers in sync.\"},{\"file\":\"src/language-js/comments/handle-comments.js\",\"line\":701,\"severity\":\"minor\",\"title\":\"Unsafe access to enclosingNode.value.body\",\"explanation\":\"For `MethodDefinition` the code does `enclosingNode.value.body` without guarding. Abstract/declare methods (e.g., `TSAbstractMethodDefinition`/`TSDeclareMethod`) can lack `value`/`body`; even with current guard (`followingNode` is BlockStatement) a malformed/missing `value` would throw TypeError instead of falling through. Use `enclosingNode.value?.body`.\"}]}","startedAt":"2026-10-16T10:12:02.505Z","finishedAt":"2026-10-16T10:13:04.991Z"}
{"id":"f8957a26-8902-40b5-bea5-558a7becb6bc","caseId":"prettier-c45fef106","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Fix correctly centralizes unquoted-attribute logic and applies it in printer-html.js for `--embedded-language-formatting off` where embed is skipped. Snapshots confirm LWC `{data}` stays unquoted and style_embed handling diverges correctly.","findings":[]},"text":"Fixing an LWC quotation quirk — now checking the parser logic for a solid verdict.I've pieced together the embedded formatting fix — now verifying it doesn't break existing quoting or unquoting rules.{\"verdict\":\"approve\",\"summary\":\"Fix correctly centralizes unquoted-attribute logic and applies it in printer-html.js for `--embedded-language-formatting off` where embed is skipped. Snapshots confirm LWC `{data}` stays unquoted and style_embed handling diverges correctly.\",\"findings\":[]}","startedAt":"2026-10-16T10:13:05.006Z","finishedAt":"2026-10-16T10:13:55.922Z"}
{"id":"93d549e0-e6c0-4de6-8a11-fa603a85b8d1","caseId":"prettier-7d3ff5d2f","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Fix correctly avoids breaking before `as`/`satisfies` by attaching multi-line block comments between the expression and type to the type (or trailing for `as const` where the type node cannot hold comments); single-line comments are intentionally excluded and snapshots show stable formatting.","findings":[]},"text":"Reviewing this comment placement fix for `as`/`satisfies`, I'll inspect the implementation for edge cases.{\"verdict\":\"approve\",\"summary\":\"Fix correctly avoids breaking before `as`/`satisfies` by attaching multi-line block comments between the expression and type to the type (or trailing for `as const` where the type node cannot hold comments); single-line comments are intentionally excluded and snapshots show stable formatting.\",\"findings\":[]}","startedAt":"2026-10-16T10:13:55.937Z","finishedAt":"2026-10-16T10:14:29.417Z"}
{"id":"35dea9b1-6e36-4038-8011-d3713eafc1ee","caseId":"recharts-3b0ec866d","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Fix correctly separates visibility (`active`) from sync-receiving state (`sourceViewBox`), preventing sparse charts from emitting counter-sync events that caused flicker; deactivation handling and suppression guard are now consistent across sync methods.","findings":[]},"text":"This sync-tooltip fix is subtle — I’ll trace the state changes for mismatched data cases.I’ve mapped the core sync flow — now I’ll check neighboring states for side effects.{\"verdict\":\"approve\",\"summary\":\"Fix correctly separates visibility (`active`) from sync-receiving state (`sourceViewBox`), preventing sparse charts from emitting counter-sync events that caused flicker; deactivation handling and suppression guard are now consistent across sync methods.\",\"findings\":[]}","startedAt":"2026-10-16T10:14:29.433Z","finishedAt":"2026-10-16T10:15:31.982Z"}
{"id":"bceaa291-8fc0-4b2f-b6ae-185f4951c20b","caseId":"recharts-0de2ec66c","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Fix correctly guards against non-string `type` propagated from Scatter data entries; numeric/invalid values now fallback to 'circle' instead of throwing in `upperFirst`, preserving custom symbol support and filtered correctly from DOM props.","findings":[]},"text":"Reviewing the proposed safeguard in Symbols — now checking source and data-flow for hidden regressions.I've mapped how scattered data can trigger the crash — now probing for edge cases and remaining safety issues.{\"verdict\":\"approve\",\"summary\":\"Fix correctly guards against non-string `type` propagated from Scatter data entries; numeric/invalid values now fallback to 'circle' instead of throwing in `upperFirst`, preserving custom symbol support and filtered correctly from DOM props.\",\"findings\":[]}","startedAt":"2026-10-16T10:15:31.997Z","finishedAt":"2026-10-16T10:16:06.334Z"}
{"id":"0d8baaf1-118e-44a7-b29c-f6daa4074059","caseId":"recharts-3a7cf75f8","kind":"control","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"Change adds visual clipping and a domain-guard to hide the active dot, but the guard incorrectly ties tooltip visibility to the computed tooltip-axis domain and breaks keyboard/fallback behavior, and clipping is conditional on `needClip`.","findings":[{"file":"src/state/selectors/combiners/combineActiveTooltipIndex.ts","line":34,"severity":"major","title":"Domain guard only checks one axis and mis-handles allowDataOverflow","explanation":"`isValueWithinDomain` is only evaluated against `selectTooltipAxisDomain`/`selectTooltipAxisDataKey`. Points that are outside the Y domain (or both axes) but inside the X domain still pass and the active dot can still be drawn outside the plot. More critically the guard runs against the final computed domain (`combineAxisDomain`), which expands the user domain when `allowDataOverflow===false` (via `extendDomain`). `combineActiveTooltipIndex` therefore returns non-null when overflow is disabled, while `test/component/Tooltip/ActiveDot.spec.tsx:286` expects suppression for both true/false. In practice with `allowDataOverflow=false` the domain `[1.01,1.15]` is expanded to include the data `[0.9,1.3]` – tooltip should remain visible per Recharts contract, but the new `selectIsTooltipActive`/`selectActiveTooltipIndex` wiring hides it, breaking existing behavior; with `allowDataOverflow=true` the intended suppression works but the test cannot pass for both branches."},{"file":"src/cartesian/Line.tsx","line":615,"severity":"major","title":"Active-dot clipping only when needClip, leaves hole when domain guard fails","explanation":"`activePointsClipPath` is `undefined` when `needClip===false` (both axes `allowDataOverflow===false`). If `combineActiveTooltipIndex` fails to suppress (e.g. overflow-disabled expanded domain case above) the active dot is rendered without any `clipPath` and can appear outside the graph. Same pattern in `src/cartesian/Area.tsx:662`. Fix should clip unconditionally (or via Y/X separately) or ensure the domain guard is reliable."},{"file":"src/state/keyboardEventsMiddleware.ts","line":42,"severity":"minor","title":"Keyboard navigation can step into out-of-domain index and get stuck","explanation":"`resolvedIndex` now respects the domain guard, but `nextIndex` is validated only against `tooltipTicks.length`. Arrow navigation can move from a valid index to an index whose datum is outside `selectTooltipAxisDomain`; the next render then yields `null` active index and the following Arrow press exits early (`!Number.isFinite(currentIndex) || currentIndex<0`) leaving the keyboard interaction stranded on an invisible/inactive index. Should validate `chartData[nextIndex]` against the domain before dispatching."},{"file":"src/state/selectors/selectors.ts","line":192,"severity":"minor","title":"selectIsTooltipActive API change from active-flag to activeIndex-null coupling","explanation":"Changed from `{isActive: tooltipInteractionState.active}` to `active && activeIndex!=null`. Callers that relied on `isActive` independent of data validity (e.g. item hover, custom tooltip triggers) now see `isActive=false` when the clamped index maps to an out-of-domain datum, changing public selector semantics and tooltip/cursor visibility."}]},"text":"Inspecting the clipping and tooltip-index changes for clipping mismatches and selection regressions.I've mapped the visual clipping strategy — now I'll verify the tooltip-index guard doesn't introduce regressions.The domain handling looks nuanced — I'll check the test failures to pinpoint any remaining gaps.Let me do a final sweep of edge cases before finalizing my review.{\"verdict\":\"request_changes\",\"summary\":\"Change adds visual clipping and a domain-guard to hide the active dot, but the guard incorrectly ties tooltip visibility to the computed tooltip-axis domain and breaks keyboard/fallback behavior, and clipping is conditional on `needClip`.\",\"findings\":[{\"file\":\"src/state/selectors/combiners/combineActiveTooltipIndex.ts\",\"line\":34,\"severity\":\"major\",\"title\":\"Domain guard only checks one axis and mis-handles allowDataOverflow\",\"explanation\":\"`isValueWithinDomain` is only evaluated against `selectTooltipAxisDomain`/`selectTooltipAxisDataKey`. Points that are outside the Y domain (or both axes) but inside the X domain still pass and the active dot can still be drawn outside the plot. More critically the guard runs against the final computed domain (`combineAxisDomain`), which expands the user domain when `allowDataOverflow===false` (via `extendDomain`). `combineActiveTooltipIndex` therefore returns non-null when overflow is disabled, while `test/component/Tooltip/ActiveDot.spec.tsx:286` expects suppression for both true/false. In practice with `allowDataOverflow=false` the domain `[1.01,1.15]` is expanded to include the data `[0.9,1.3]` – tooltip should remain visible per Recharts contract, but the new `selectIsTooltipActive`/`selectActiveTooltipIndex` wiring hides it, breaking existing behavior; with `allowDataOverflow=true` the intended suppression works but the test cannot pass for both branches.\"},{\"file\":\"src/cartesian/Line.tsx\",\"line\":615,\"severity\":\"major\",\"title\":\"Active-dot clipping only when needClip, leaves hole when domain guard fails\",\"explanation\":\"`activePointsClipPath` is `undefined` when `needClip===false` (both axes `allowDataOverflow===false`). If `combineActiveTooltipIndex` fails to suppress (e.g. overflow-disabled expanded domain case above) the active dot is rendered without any `clipPath` and can appear outside the graph. Same pattern in `src/cartesian/Area.tsx:662`. Fix should clip unconditionally (or via Y/X separately) or ensure the domain guard is reliable.\"},{\"file\":\"src/state/keyboardEventsMiddleware.ts\",\"line\":42,\"severity\":\"minor\",\"title\":\"Keyboard navigation can step into out-of-domain index and get stuck\",\"explanation\":\"`resolvedIndex` now respects the domain guard, but `nextIndex` is validated only against `tooltipTicks.length`. Arrow navigation can move from a valid index to an index whose datum is outside `selectTooltipAxisDomain`; the next render then yields `null` active index and the following Arrow press exits early (`!Number.isFinite(currentIndex) || currentIndex<0`) leaving the keyboard interaction stranded on an invisible/inactive index. Should validate `chartData[nextIndex]` against the domain before dispatching.\"},{\"file\":\"src/state/selectors/selectors.ts\",\"line\":192,\"severity\":\"minor\",\"title\":\"selectIsTooltipActive API change from active-flag to activeIndex-null coupling\",\"explanation\":\"Changed from `{isActive: tooltipInteractionState.active}` to `active && activeIndex!=null`. Callers that relied on `isActive` independent of data validity (e.g. item hover, custom tooltip triggers) now see `isActive=false` when the clamped index maps to an out-of-domain datum, changing public selector semantics and tooltip/cursor visibility.\"}]}","startedAt":"2026-10-16T10:16:06.349Z","finishedAt":"2026-10-16T10:18:08.671Z"}
{"id":"4b8d21e6-6c06-463f-ba94-98f76294f0d2","caseId":"svelte-a23599a19","kind":"control","ok":true,"repaired":false,"review":{"verdict":"request_changes","summary":"Change correctly hides component-stack adjustment from boundary handlers but defers mutation so unhandled synchronous creation errors are now thrown without the DEV adjustment (pre-PR they had added message/stack). Only invoke_error_boundary applies adjustments.","findings":[{"file":"packages/svelte/src/internal/client/error-handling.js","line":26,"severity":"major","title":"Unhandled creation-time errors lose DEV adjustment","explanation":"handle_error stores adjustments in WeakMap for EFFECT_RAN==0 errors but throw at line 26 (and re-throw chain via effects.js create_effect catch) never calls apply_adjustments. Previously adjust_error mutated immediately so top-level throws showed \"\\n\\tin ...\" and filtered stack. Now only invoke_error_boundary (EFFECT_RAN branch) applies, so any error thrown during initial branch/block creation with no enclosing svelte:boundary reaches the mount caller unadjusted, regressing dev diagnostics."},{"file":"packages/svelte/src/internal/client/error-handling.js","line":19,"severity":"minor","title":"get_adjustments can return undefined but is stored","explanation":"get_adjustments returns early (undefined) when message descriptor is non-configurable; adjustments.set(error, undefined) marks it as seen and apply_adjustments later no-ops. Same behavior as old WeakSet but stores falsy value; explicit sentinel or avoiding set on undefined would be clearer and avoids weak-map entry that prevents future re-computation if effect context changes."}]},"text":"Fixing the boundary error display — now tracing how adjusted messages leak into the UI.I spotted how errors bubble through the new adjustment cache — now I'm checking for edge cases in the fallback logic.The core boundary fix looks sound — now I'm scanning the remaining test cases for any leftover expectations that need updating.{\"verdict\":\"request_changes\",\"summary\":\"Change correctly hides component-stack adjustment from boundary handlers but defers mutation so unhandled synchronous creation errors are now thrown without the DEV adjustment (pre-PR they had added message/stack). Only invoke_error_boundary applies adjustments.\",\"findings\":[{\"file\":\"packages/svelte/src/internal/client/error-handling.js\",\"line\":26,\"severity\":\"major\",\"title\":\"Unhandled creation-time errors lose DEV adjustment\",\"explanation\":\"handle_error stores adjustments in WeakMap for EFFECT_RAN==0 errors but throw at line 26 (and re-throw chain via effects.js create_effect catch) never calls apply_adjustments. Previously adjust_error mutated immediately so top-level throws showed \\\"\\\\n\\\\tin ...\\\" and filtered stack. Now only invoke_error_boundary (EFFECT_RAN branch) applies, so any error thrown during initial branch/block creation with no enclosing svelte:boundary reaches the mount caller unadjusted, regressing dev diagnostics.\"},{\"file\":\"packages/svelte/src/internal/client/error-handling.js\",\"line\":19,\"severity\":\"minor\",\"title\":\"get_adjustments can return undefined but is stored\",\"explanation\":\"get_adjustments returns early (undefined) when message descriptor is non-configurable; adjustments.set(error, undefined) marks it as seen and apply_adjustments later no-ops. Same behavior as old WeakSet but stores falsy value; explicit sentinel or avoiding set on undefined would be clearer and avoids weak-map entry that prevents future re-computation if effect context changes.\"}]}","startedAt":"2026-10-16T10:18:08.688Z","finishedAt":"2026-10-16T10:19:45.356Z"}
{"id":"84c2bbfc-1bb0-4d0e-991f-1d22689034f5","caseId":"svelte-68d4f9c3b","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"PR correctly awaits async/blocked dependencies for bindings, static props and style directives, and fixes hydration end-marker rewind. No crashing regressions observed; remaining gaps are narrow edge-cases.","findings":[{"file":"packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js","line":162,"severity":"minor","title":"Functional binding visit skips function scope","explanation":"For `bind:value={get,set}` where get/set are ArrowFunctionExpressions, the code visits only `get.body`/`set.body` to avoid resetting `context.state.expression`. This bypasses creation of the arrow-function scope, so a setter parameter like `v => value = v` that shadows an outer state variable `v` with a blocker will be treated as a reference to the outer `v`, incorrectly adding its blocker/dependencies. Old code ignored functional bindings entirely, so this is an improvement but still mishandles shadowing. Affects only names colliding shadowing cases."},{"file":"packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js","line":320,"severity":"minor","title":"Spread path for BindDirective misses blocker tracking","explanation":"`build_element_attributes` now calls `transform(…, attribute.metadata.expression)` for BindDirective so server async-blockers are recorded. The spread-object path `build_spread_object` (used when an element has `...spread`) still does `context.visit(attribute.expression)` without `transform`/`check_blockers` for `BindDirective`. A `<div {...spread} bind:value={asyncVar}>` that goes through spread handling would therefore not contribute its blocker to `PromiseOptimiser`, breaking the async fix for that less common path."},{"file":"packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js","line":452,"severity":"nit","title":"Shorthand style-directive spread case ignores blockers","explanation":"In `prepare_element_spread` shorthand branches `directive.value===true ? b.id(name)` and `build_attr_style`/`build_attr_class` for spread cases do not wrap via `transform`. If a spread-element uses shorthand `style:color` where the implicit binding has a blocker, no blocker is registered, unlike the non-spread path fixed in `BuildStyleDirective/RegularElement`. Benign for typical templates."}]},"text":"Pull request touches async binding and hydration — I'll check it for race conditions and regressions.I've mapped how the compiler handles async bindings — next I'll verify the runtime logic for edge cases.The main transforms look solid — I'll do a final sweep for any handling gaps.{\"verdict\":\"approve\",\"summary\":\"PR correctly awaits async/blocked dependencies for bindings, static props and style directives, and fixes hydration end-marker rewind. No crashing regressions observed; remaining gaps are narrow edge-cases.\",\"findings\":[{\"file\":\"packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js\",\"line\":162,\"severity\":\"minor\",\"title\":\"Functional binding visit skips function scope\",\"explanation\":\"For `bind:value={get,set}` where get/set are ArrowFunctionExpressions, the code visits only `get.body`/`set.body` to avoid resetting `context.state.expression`. This bypasses creation of the arrow-function scope, so a setter parameter like `v => value = v` that shadows an outer state variable `v` with a blocker will be treated as a reference to the outer `v`, incorrectly adding its blocker/dependencies. Old code ignored functional bindings entirely, so this is an improvement but still mishandles shadowing. Affects only names colliding shadowing cases.\"},{\"file\":\"packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js\",\"line\":320,\"severity\":\"minor\",\"title\":\"Spread path for BindDirective misses blocker tracking\",\"explanation\":\"`build_element_attributes` now calls `transform(…, attribute.metadata.expression)` for BindDirective so server async-blockers are recorded. The spread-object path `build_spread_object` (used when an element has `...spread`) still does `context.visit(attribute.expression)` without `transform`/`check_blockers` for `BindDirective`. A `<div {...spread} bind:value={asyncVar}>` that goes through spread handling would therefore not contribute its blocker to `PromiseOptimiser`, breaking the async fix for that less common path.\"},{\"file\":\"packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js\",\"line\":452,\"severity\":\"nit\",\"title\":\"Shorthand style-directive spread case ignores blockers\",\"explanation\":\"In `prepare_element_spread` shorthand branches `directive.value===true ? b.id(name)` and `build_attr_style`/`build_attr_class` for spread cases do not wrap via `transform`. If a spread-element uses shorthand `style:color` where the implicit binding has a blocker, no blocker is registered, unlike the non-spread path fixed in `BuildStyleDirective/RegularElement`. Benign for typical templates.\"}]}","startedAt":"2026-10-16T10:19:45.371Z","finishedAt":"2026-10-16T10:21:52.644Z"}
{"id":"1d68d356-4391-433c-8b21-da2b7097637d","caseId":"tldraw-5212a87d6","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Fix correctly updates Erasing hit resolution to skip excluded outermost groups and target the nearest non-excluded ancestor/leaf, matching the described bug (starting drag inside group bounds previously erased nothing). Logic handles frames/locked shapes, preserves focused-group semantics and final exclusion filter; new tests align with expected erase-child vs erase-group behavior.","findings":[]},"text":"Reviewing your eraser fix for grouped shapes — now checking the hit-resolution logic for edge cases.{\"verdict\":\"approve\",\"summary\":\"Fix correctly updates Erasing hit resolution to skip excluded outermost groups and target the nearest non-excluded ancestor/leaf, matching the described bug (starting drag inside group bounds previously erased nothing). Logic handles frames/locked shapes, preserves focused-group semantics and final exclusion filter; new tests align with expected erase-child vs erase-group behavior.\",\"findings\":[]}","startedAt":"2026-10-16T10:25:51.546Z","finishedAt":"2026-10-16T10:26:35.101Z"}
{"id":"a0653329-a777-4226-a2da-e287e764faa6","caseId":"tldraw-5221da51b","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Fix correctly batches record-scope updates to avoid SQLite live-cursor revisits and does the same for the arrow ExtractBindings storage migration; behavior is preserved and regression tests verify exactly-once execution.","findings":[{"file":"packages/store/src/lib/StoreSchema.ts","line":635,"severity":"minor","title":"Final document-scope cleanup still mutates during iteration","explanation":"The loop `for (const [id, state] of storage.entries()) { if (scope !== 'document') storage.delete(id) }` deletes while iterating a live SQLite cursor. Like the record-migration bug this PR fixes, SQLite docs say modifying a table during an active SELECT is undefined and can skip rows, leaving non-document legacy records undeleted. Should collect ids first then delete, consistent with the record-migration fix."},{"file":"packages/tlschema/src/store-migrations.ts","line":72,"severity":"minor","title":"Other storage-scope migrations still delete during live iteration","explanation":"Three storage migrations (RemoveCodeAndIconShapeTypes, RemoveTLUserAndPresenceAndAddPointer, RemoveUserDocument) iterate `storage.entries()` and call `storage.delete(id)` inside the loop. Same live-iterator hazard as the fixed arrow migration exists here; a batched-collect-then-delete pattern would make them safe for SQLiteSyncStorage. Low risk because they are old retroactive:false migrations, but inconsistent with the new best-practice established by this PR."}]},"text":"Reviewing the double-migration fix — checking how the iteration batching handles live SQLite cursors.Found the main fix looks sound — now digging into transaction details and cleanup behavior.Quickly verifying there are no other storage migrations that might need the same treatment.{\"verdict\":\"approve\",\"summary\":\"Fix correctly batches record-scope updates to avoid SQLite live-cursor revisits and does the same for the arrow ExtractBindings storage migration; behavior is preserved and regression tests verify exactly-once execution.\",\"findings\":[{\"file\":\"packages/store/src/lib/StoreSchema.ts\",\"line\":635,\"severity\":\"minor\",\"title\":\"Final document-scope cleanup still mutates during iteration\",\"explanation\":\"The loop `for (const [id, state] of storage.entries()) { if (scope !== 'document') storage.delete(id) }` deletes while iterating a live SQLite cursor. Like the record-migration bug this PR fixes, SQLite docs say modifying a table during an active SELECT is undefined and can skip rows, leaving non-document legacy records undeleted. Should collect ids first then delete, consistent with the record-migration fix.\"},{\"file\":\"packages/tlschema/src/store-migrations.ts\",\"line\":72,\"severity\":\"minor\",\"title\":\"Other storage-scope migrations still delete during live iteration\",\"explanation\":\"Three storage migrations (RemoveCodeAndIconShapeTypes, RemoveTLUserAndPresenceAndAddPointer, RemoveUserDocument) iterate `storage.entries()` and call `storage.delete(id)` inside the loop. Same live-iterator hazard as the fixed arrow migration exists here; a batched-collect-then-delete pattern would make them safe for SQLiteSyncStorage. Low risk because they are old retroactive:false migrations, but inconsistent with the new best-practice established by this PR.\"}]}","startedAt":"2026-10-16T10:26:35.119Z","finishedAt":"2026-10-16T10:27:31.376Z"}
{"id":"fb91c8e1-2072-405d-94b0-a1858ed3c30f","caseId":"tldraw-fed7148ac","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Conditional `scale !== 1` guard correctly avoids unconditional `transform: scale(1)` compositor layer while preserving visual result at scale=1 via HTMLContainer/CSS 100% fallback; arrow translate preserved.","findings":[]},"text":"Skipping the idle transform — now checking the label logic for performance tradeoffs.I’ve mapped how the labels are styled — now digging into edge cases around the scaling optimization.{\"verdict\":\"approve\",\"summary\":\"Conditional `scale !== 1` guard correctly avoids unconditional `transform: scale(1)` compositor layer while preserving visual result at scale=1 via HTMLContainer/CSS 100% fallback; arrow translate preserved.\",\"findings\":[]}","startedAt":"2026-10-16T10:27:31.395Z","finishedAt":"2026-10-16T10:28:18.167Z"}
{"id":"659c5cf3-4760-4c82-95b0-f4e37f2f35ea","caseId":"tiptap-526365a68","kind":"control","ok":true,"repaired":false,"review":{"verdict":"approve","summary":"Multi-mention serialization correctly adds conditional `mentionSuggestionChar` handling with `skipIfDefault` and `char` alias mapping; backward compatibility for string allowlist and legacy attribute names is preserved with no regressions detected.","findings":[]},"text":"Reviewing this multi-mention markdown fix — checking the serialization changes for hidden edge cases.{\"verdict\":\"approve\",\"summary\":\"Multi-mention serialization correctly adds conditional `mentionSuggestionChar` handling with `skipIfDefault` and `char` alias mapping; backward compatibility for string allowlist and legacy attribute names is preserved with no regressions detected.\",\"findings\":[]}","startedAt":"2026-10-16T10:28:18.180Z","finishedAt":"2026-10-16T10:29:03.958Z"}
