# Toon — Known Issues

For planned/actionable work see `TOON_TODO.md`.

## ACCORE-TOON-I-B7L4: `Compact` preset XML doc contradicts code behaviour

**Severity:** Trivial (doc-only, no runtime effect) · **Status:** Open (direction not decided) · **Area:** `Serializers/Toons/AcToonSerializerOptions.cs`

### Description
The XML doc on `AcToonSerializerOptions.Compact` claims *"Minimal output, no meta, no indentation"*, but the property initializer sets `UseIndentation = true`. Real output is DataOnly + no type names + no reference handling **with** indentation whitespace. Consumers reading IntelliSense expect a tighter output than they receive.

```csharp
/// <summary>
/// Compact mode: Minimal output, no meta, no indentation.   ← doc says no indent
/// ...
/// </summary>
public static AcToonSerializerOptions Compact => new()
{
    Mode = ToonSerializationMode.DataOnly,
    UseMeta = false,
    UseIndentation = true,                                     ← code sets true
    ...
};
```

### Root cause
Likely the XML comment predates a later preset revision that flipped `UseIndentation` to `true`. The comment was not updated to match.

### Fix options (undecided)
- **(a) Fix the doc** — update the XML comment to match current behaviour. "Compact" then means "DataOnly, no type names, no refs" — not literal whitespace-free.
- **(b) Fix the code** — flip `UseIndentation` to `false` in the preset to honour original intent. Output-breaking change for anyone relying on current `Compact` formatting.

### Known workaround
Build a custom options instance when truly token-minimal output is needed:
```csharp
var trulyCompact = new AcToonSerializerOptions {
    Mode = ToonSerializationMode.DataOnly,
    UseMeta = false,
    UseIndentation = false,
    OmitDefaultValues = true,
    WriteTypeNames = false,
    ReferenceHandling = ReferenceHandlingMode.None
};
```

`TOON_OPTIONS.md` already notes this divergence next to the `Compact` preset; decision here governs whether it persists or gets resolved.

### Related TODO
None yet — fix direction not decided.

## ACCORE-TOON-I-X3H2: `Age` inference substring false positive

**Severity:** Minor (constraint-only, no runtime effect) · **Status:** Open · **Area:** `Serializers/Toons/AcToonSerializer.Descriptions.cs` → `GetPropertyConstraints` / `GetInferredConstraints`

### Description
Integer properties whose name **contains** the substring `age` (case-insensitive, not word-bounded) erroneously receive the `range: 0-150` constraint from the `Age` inference rule. False positives observed: `LanguageId`, and by pattern `Package*`, `Image*`, `Page*`, `Message*`, `Storage*`, `Stage*`.

Observed in a 2026-04-24 Toon dump:
```
LanguageId: int?
  constraints: "range: 0-150"
```

### Root cause
```csharp
if (propertyName.Contains("Age", StringComparison.OrdinalIgnoreCase))
    constraints.Add("range: 0-150");
```

`"LanguageId".Contains("Age", OrdinalIgnoreCase)` returns `true` — the substring `age` sits at position 5-7 inside `Language`. The rule's intent was to match an `Age`-named property, but `Contains` has no word-boundary awareness.

### Fix options
- **(a)** Exact match: `propertyName.Equals("Age", OrdinalIgnoreCase)` — loses legitimate matches like `CustomerAge`, `MinAge`.
- **(b)** Suffix: `propertyName.EndsWith("Age", OrdinalIgnoreCase)` — still matches `Language` (ends in `age`).
- **(c)** Word-boundary regex — accurate but adds a regex pass per property.
- **(d)** `EndsWith("Age")` + preceded-by-uppercase guard — `CustomerAge` ok (`r`→`A` capital transition), `Language` rejected (`u`→`a` lowercase transition).

Similar risk applies to other substring-based rules in `Descriptions.cs` (`Email`, `Phone`, `Address`, `Url`, `Date`, `Version`, `CreatedAt`, `UpdatedAt`, `DeletedAt`) — audit them under the same word-boundary lens.

### Known workaround
Explicit `[ToonDescription(Constraints = "...")]` on the affected property (replace mode — no placeholder).

### Related TODO
None yet.

## ACCORE-TOON-I-P6V5: Property override duplicated on inheritance in `@types`

**Severity:** Minor (schema correctness; LLM may misinterpret) · **Status:** Closed (2026-05-31) · **Area:** `TypeMetadataBase.BuildPropertyList` (shared property enumeration — Binary/JSON/Toon).

### Description
When a derived class uses `override` on a property inherited from its base, both the derived and the base version appear in the `@types` schema — two entries for the same logical member, each with its own `business-logic` string.

Observed in a 2026-04-24 Toon dump on `OrderItemPallet`:
```
OrderItemPallet:
    ...
    MeasuringStatus: MeasuringStatus
      business-logic: "get => IsAudited ? MeasuringStatus.Audited : base.MeasuringStatus"
      constraints: "readonly, not-mapped"
    ...
    MeasuringStatus: MeasuringStatus
      business-logic: "get => IsMeasured ? MeasuringStatus.Finnished : Id > 0 ? ..."
      constraints: "readonly, not-mapped"
```

Only affected when a class uses `override` on a property. Sibling non-overriding classes (`ShippingItemPallet`, `StockTakingItemPallet`) emit the property once as expected.

### Root cause (confirmed)
`TypeMetadataBase.BuildPropertyList` walks the inheritance chain with `BindingFlags.DeclaredOnly` per level and `AddRange`s each level with **no name-dedup**. An `override`d (or `new`-shadowed) property is declared at every level, so it was collected once per level. (Not the flat `GetProperties(Public|Instance)` — that *would* dedupe; the explicit `DeclaredOnly` + `BaseType` walk is what duplicates.)

Scope is wider than Toon: the shared list feeds **both** the runtime AcBinary path and Toon. The SGen path already deduped via its own `HashSet<string> seen` (`AcBinarySourceGenerator.GetAllSerializablePropertySymbols`). `MeasuringStatus` surfaced only in Toon because Toon reads `ReadableProperties` while AcBinary uses `WritableProperties`, where the readonly `MeasuringStatus` is dropped by the `CanWrite` filter — but any **writable** override was duplicated on the runtime wire too, and diverged from the deduped SGen output on mixed runtime↔SGen paths.

### Fix options
- **(a)** Filter in the metadata build step: group by `Name`, keep the most-derived override (lowest inheritance distance from the target type).
- **(b)** Walk the inheritance chain with `BindingFlags.DeclaredOnly` per level, collecting names into a set; skip if already seen. ← chosen

### Resolution (2026-05-31)
Option (b) in `TypeMetadataBase.BuildPropertyList`: a `HashSet<string> seen` added as the final `.Where` predicate (`seen.Add(p.Name)`). The derived→base walk means the most-derived declaration is seen first and wins; later (base) levels skip the already-seen name. Mirrors the SGen `GetAllSerializablePropertySymbols`, so the runtime and generated property orders stay bit-identical (closes the latent runtime-writable↔SGen wire divergence). Zero behaviour change for non-override types (every name unique → `seen.Add` always true).

### Related TODO
None yet.

## ACCORE-TOON-I-K4Z9: Property order in `@types` is not pure alphabetical

**Severity:** Minor (readability / predictability) · **Status:** Open · **Area:** Property ordering — likely `AcToonSerializer.ToonSerializeTypeMetadata.cs`, inherited from the shared base in `Serializers/` root.

### Description
Per-class property emission in `@types` is **hierarchy-aware derived→base**, with alphabetical order within each inheritance level. The inherited `Id` property therefore appears at the very end of every class definition, instead of its alphabetical slot between `H…` and `J…`.

Observed in a 2026-04-24 Toon dump on `Customer` (simplified):
```
Customer:
    Active, AdminComment, ..., ZipPostalCode,    ← own properties, alphabetical
    Id                                            ← inherited, placed LAST
```

And on `OrderItemPallet` (which has multi-level inheritance):
```
OrderItemPallet:
    AverageWeight, IsAudited, MeasuringStatus, ..., TrayQuantity,  ← derived-class props
    Created, CreatorId, ForeignKey, ..., TrayQuantity,             ← base class props
    Id                                                              ← base-base entity
```

### Expected behavior
**Pure alphabetical order across all properties** of the type, regardless of inheritance level. For Toon's LLM-readability goal, a predictable flat order matters more than hierarchy preservation.

### Docs inconsistency
`Serializers/README.md` "Property ordering" section states: *"Hierarchy-aware (base→derived) then alphabetical. Ensures stable property indices across type versions."* Two mismatches with actual behavior:

1. **Direction reversed** — real output is `derived→base`, not `base→derived` as the doc claims.
2. **Rationale irrelevant to Toon** — "stable property indices across type versions" is a wire-format concern for AcBinary. Toon is a descriptive schema format where property order is cosmetic, not semantic.

### Fix options
- **(a)** Override the property ordering specifically in `ToonSerializeTypeMetadata` to plain `OrderBy(p => p.Name)` — Toon diverges from the Binary/JSON shared ordering. Recommended: AcBinary needs the hierarchy-aware order for wire stability; Toon does not, and can opt out.
- **(b)** Fix the direction in the shared base (`base→derived` as the docs claim) — partial fix, still not pure alphabetical.
- **(c)** Accept current behavior and update `Serializers/README.md` to match observed reality — reject the issue. Not recommended: the user expectation is pure alphabetical, and the docs-vs-code gap is real either way.

### Known workaround
None — consumers must tolerate the current order.

### Related TODO
None yet. Fix also requires reconciling `Serializers/README.md` with the chosen direction.

## ACCORE-TOON-I-S4Q9: `MaxDepthBehavior` option silently ignored

**Severity:** Minor (silent feature gap — option visible, no effect) · **Status:** Open · **Area:** `Serializers/Toons/` (engine-wide; depth handling)

### Description

`MaxDepthBehavior` was introduced on the base `AcSerializerOptions` (2026-05) to dispatch the action at the `MaxDepth` boundary:

- `Truncate` — write `Null` (silent shallow serialization).
- `Throw` — `InvalidOperationException` (cycle/bug detection, **base default**).
- `Disable` — skip the depth safety net entirely (max perf, dev guarantees no cycles).

`AcBinarySerializer` honors all three. `AcToonSerializer` inherits the option through `AcToonSerializerOptions : AcSerializerOptions` but its depth-handling code path **does not dispatch on the value** — Toon always truncates to `null` regardless of what the consumer set.

```csharp
var opts = new AcToonSerializerOptions
{
    MaxDepth = 5,
    MaxDepthBehavior = MaxDepthBehavior.Throw  // ← silently ignored
};
// Output still truncates to `null` at depth 5; no exception thrown.
```

### Root cause

Toon's depth handling predates the `MaxDepthBehavior` introduction. The pre-existing logic emits `null` at `depth > MaxDepth` (see `TOON_IMPLEMENTATION.md` reference scanning section). When the new enum was added at the base, only `AcBinarySerializer` was wired to dispatch on it; the Toon writer was not updated.

### Known workaround

If the application logic needs the `Throw` behavior on Toon: set a small `MaxDepth` and post-process the output to detect `null` truncation markers and raise the error at the consumer layer. For `Disable`, leave `MaxDepth = 255` (effective no-op given typical graph depths).

### Related TODO

`TOON_TODO.md#accore-toon-t-w8b5` — adopt `MaxDepthBehavior` dispatch in Toon's depth handling.

## ACCORE-TOON-B-R8K3: `OnlyId` reference handling not implemented — Toon dedups by reference identity only

**Severity:** Major (silent correctness gap in the **default** mode; latent behind the SignalR/AcBinary path) · **Status:** Open · **Area:** `Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs` (tracking maps) + `AcToonSerializer.cs` (`ScanReferences`)

### Description
`AcToonSerializerOptions` inherits `ReferenceHandling` with a **default of `OnlyId`**, which promises IId-value-based deduplication: two *distinct instances* sharing the same `IId.Id` collapse to one `@N` definition + `@ref:N`. Toon does **not** implement this. Its context tracks purely by **reference identity** (`ReferenceEqualityComparer`); `OnlyId` and `All` behave identically. Distinct instances with the same `Id` are serialized in full, repeatedly, with no `@ref` markers.

```csharp
var opts = new AcToonSerializerOptions { ReferenceHandling = ReferenceHandlingMode.OnlyId };
// graph: two DIFFERENT Customer instances, both Id = 1
// Expected (OnlyId): @1 Customer { ... }  …later…  @ref:1
// Actual: both written in full, no dedup
```

### Why it stayed hidden
Real data reached Toon **after** an AcBinary round-trip (SignalR transport). AcBinary *does* implement `OnlyId`, so its deserialization had already unified same-`Id` entities into a **single shared instance**. Toon's reference-identity dedup then worked on those shared instances, giving the appearance of correct IId handling. Any path that does NOT go through AcBinary (direct linq2db materialization, hand-built graphs, unit tests) exposes the gap. The SignalR layer masked the bug — it did not fix it.

### Root cause
`ToonSerializationContext` has used `Dictionary<object,int>` / `HashSet<object>` with `ReferenceEqualityComparer` since the genesis commit (`bbb21db`). No commit ever introduced IId logic in the Toon folder (`git log --all -S "IId" -- Serializers/Toons/` → empty; `grep "IId"` → 0 hits). The shared typed `IdentityMap` (Int32/Int64/Guid) that `AcBinarySerializer` uses for `OnlyId` is **not** wired into Toon. `TrackForScanning` / `ShouldWriteRef` / `TryGetExistingRef` do not dispatch on `ReferenceHandling`.

### Known workaround
- Feed Toon a graph whose same-`Id` entities are already a single instance — e.g. after an AcBinary serialize→deserialize round-trip, or by sharing instances when constructing the graph.
- Do **not** rely on Toon to deduplicate distinct instances by `Id`.

### Fix direction (agreed, not yet scheduled)
Wire Toon's `OnlyId` path to the shared `IdentityMap` (mirror `AcBinarySerializer` — reuse the proven infra, don't reinvent), so `OnlyId` matches by `IId.Id` value while `All` keeps reference identity. Requires `OnlyId`/`All` to diverge in the context + dispatch on `ReferenceHandling`. A `TOON_TODO` entry to be created when the fix is scheduled.

## Issue entry template

```
## ISSUE-NN: Short title

**Severity:** Trivial / Low / Minor / Major · **Status:** Open / Documented / Mitigated · **Area:** <subsystem>

### Description
What breaks, and under what conditions.

### Root cause
Why it happens (code location + design mismatch).

### Known workaround
Steps a consumer can take until fixed.

### Related TODO
`TOON_TODO.md#todo-NN` (if applicable).
```
