Skip to content

fix: support heterogeneous range items with per-item statics#75

Merged
adnaan merged 3 commits intomainfrom
fix/heterogeneous-range-statics
Dec 22, 2025
Merged

fix: support heterogeneous range items with per-item statics#75
adnaan merged 3 commits intomainfrom
fix/heterogeneous-range-statics

Conversation

@adnaan
Copy link
Copy Markdown
Contributor

@adnaan adnaan commented Dec 21, 2025

Summary

  • Fix rendering issues when range items have different conditional branches (e.g., different input types in a form)
  • Add StaticsMap to RangeData for hash-based deduplication of per-item statics
  • Each item with different statics gets a _sk (statics key) referencing its statics in StaticsMap

Problem

When range items produce different statics due to conditionals (like {{if eq .Type "number"}}...{{else}}...{{end}}), only the first item's statics were used for all items. This caused:

  • Wrong input types being rendered (text input statics used for number input)
  • Browser warnings like "The specified value 'World' cannot be parsed"

Solution

  • Collect statics from each range item during tree building
  • Hash statics arrays to detect unique variants
  • For homogeneous ranges (all same statics): use existing single s format
  • For heterogeneous ranges: use new sm (statics map) with per-item _sk keys

Wire Format

Homogeneous (unchanged):

{"d": [...items...], "s": ["<li>", "</li>"]}

Heterogeneous (new):

{
  "d": [{"_sk": "a1b2", "0": "World"}, {"_sk": "c3d4", "0": "5"}],
  "s": ["<li>", "</li>"],
  "sm": {"a1b2": ["<input type=\"text\"..."], "c3d4": ["<input type=\"number\"..."]}
}

Test plan

  • All existing livetemplate tests pass
  • All existing livemdtools E2E tests pass
  • TestExecArgsForm verifies heterogeneous form inputs render correctly

Related PRs

  • livetemplate/client: Same branch name with client-side rendering changes

🤖 Generated with Claude Code

When range items have different conditional branches (like different input
types in a form), they produce different statics. Previously, only the first
item's statics were used for all items, causing rendering issues.

Changes:
- Add StaticsMap to RangeData for hash-based deduplication of statics
- Collect statics from each item and detect heterogeneous ranges
- Add _sk (statics key) to items referencing their statics in StaticsMap
- Update MarshalJSON/UnmarshalJSON to include sm (statics map) in wire format
- Update test to expect Range.Statics in wire format

Wire format for heterogeneous ranges:
{"d": [{"_sk": "hash1", ...}, {"_sk": "hash2", ...}], "s": [...], "sm": {...}}

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude Bot commented Dec 21, 2025

Code Review: Heterogeneous Range Items Support

This PR introduces support for range items with different conditional branches by implementing per-item statics through a hash-based deduplication system. Overall, this is a well-designed and thoroughly implemented feature that solves a real rendering issue. Here's my detailed review:


Strengths

1. Clean Architecture

  • The separation between homogeneous (existing format) and heterogeneous (new format) ranges is elegant
  • Backward compatibility is maintained for the common case (all items same statics)
  • The wire format extension (sm for statics map) is intuitive and follows existing conventions

2. Efficient Implementation

  • Hash-based deduplication: Using SHA-256 with 8-char truncation is a smart balance between collision resistance and compactness
  • Zero allocation optimization: extractItemDynamics reuses existing dynamics map (line 312-318 in range.go)
  • Early exit optimization: Homogeneous check stops at first hash mismatch (line 207-212 in range.go)

3. Robust Error Handling

  • UnmarshalJSON properly validates types at every step (lines 407-431 in types.go)
  • Clear error messages with context (e.g., "invalid statics map entry at %s[%d]")

4. Comprehensive Cloning

  • Clone() properly handles the new StaticsMap field (lines 550-558 in types.go)
  • Deep copies prevent shared reference bugs

🔍 Potential Issues & Questions

1. Hash Collision Risk (Low Priority)

Location: internal/parse/range.go:20

return hex.EncodeToString(h.Sum(nil))[:8] // 8 chars is enough for dedup

Concern: With 8 hex chars (32 bits), birthday paradox gives ~50% collision probability at √(2^32) ≈ 65,536 unique statics variants.

Questions:

  • Have you considered the realistic upper bound on unique statics per range?
  • For forms with many conditional input types, could this become an issue?

Recommendation:

  • Consider 12 chars (48 bits) for better safety margin, or
  • Document the collision risk and acceptable use cases, or
  • Add collision detection (check if staticsMap[hash] exists with different content)

2. Memory Overhead for Heterogeneous Ranges

Location: internal/parse/range.go:245-248

For a range with N items and K unique statics variants:

  • Homogeneous: 1 × statics array
  • Heterogeneous: K × statics arrays + N × 8-byte hash strings

Questions:

  • Have you benchmarked memory usage for large heterogeneous ranges (e.g., 1000+ items)?
  • Is there a pathological case where memory overhead becomes prohibitive?

Recommendation: Consider documenting performance characteristics or adding benchmarks.

3. Test Coverage Gap

Location: PR description mentions TestExecArgsForm but:

$ go test -run TestExecArgsForm
testing: warning: no tests to run

Questions:

  • Is this test in a different repository (livemdtools)?
  • Should there be a test in this repo demonstrating heterogeneous ranges?

Recommendation: Add a unit test in this repo that:

  • Creates a range with 2+ items having different statics
  • Verifies StaticsMap is populated
  • Verifies _sk keys are present in items
  • Verifies wire format includes sm field

4. Client-Side Changes Required

Location: PR description mentions "Related PRs: livetemplate/client"

Questions:

  • Is the client PR merged/ready?
  • What happens if a client without sm support receives heterogeneous updates?

Recommendation: Document the version compatibility requirement.


🛡️ Security Considerations

No Vulnerabilities Detected

  • SHA-256 is used for deduplication (not security), so cryptographic strength is appropriate
  • No user input is directly hashed (only generated statics)
  • JSON marshaling/unmarshaling properly validates types

⚠️ Minor Consideration

If statics contain user-controlled data (unlikely but possible through template functions), collision attacks are theoretically possible. However:

  • This is a rendering optimization, not security boundary
  • Worst case: incorrect rendering, not privilege escalation
  • Acceptable risk for this use case

📊 Code Quality

Excellent

  • Clear naming: rangeItemWithStatics, buildRangeTreeWithStatics, etc.
  • Well-commented: Hash function, struct fields, and algorithm steps explained
  • Idiomatic Go: Proper error handling, clean struct initialization
  • Follows project conventions from CLAUDE.md

Minor Style Notes

  1. Line 268: buildRangeTree marked deprecated but still present. Consider:

    • Adding // Deprecated: comment for godoc
    • Or removing if unused (run go tool cover to check)
  2. Line 260: Statics: nil is redundant (zero value), but explicit nil is clearer here. Good choice.


🧪 Testing Recommendation

Add this test to internal/parse/range_test.go:

func TestBuildRangeTreeWithStatics_Heterogeneous(t *testing.T) {
    items := []rangeItemWithStatics{
        {
            tree:    &TreeNode{Dynamics: map[string]interface{}{"0": "foo"}, Statics: []string{"<input type=\"text\""}},
            statics: []string{"<input type=\"text\""},
            hash:    "abc123",
        },
        {
            tree:    &TreeNode{Dynamics: map[string]interface{}{"0": "5"}, Statics: []string{"<input type=\"number\""}},
            statics: []string{"<input type=\"number\""},
            hash:    "def456",
        },
    }
    
    ctx := NewContext()
    tree, err := buildRangeTreeWithStatics(items, ctx)
    
    require.NoError(t, err)
    require.NotNil(t, tree.Range)
    require.Len(t, tree.Range.StaticsMap, 2)
    require.Contains(t, tree.Range.StaticsMap, "abc123")
    require.Contains(t, tree.Range.StaticsMap, "def456")
    
    // Verify items have _sk keys
    item0 := tree.Range.Items[0].(*TreeNode)
    require.Equal(t, "abc123", item0.Dynamics["_sk"])
}

📈 Performance

Should be Fast

  • Hash computation: O(n) where n = total chars in statics (small)
  • Deduplication: O(k) where k = unique variants (typically 2-5)
  • Memory allocation: Minimized through map reuse

📊 Benchmark Suggestion

go test -bench=BenchmarkBuildRangeTreeWithStatics -benchmem

Final Verdict

APPROVE with minor suggestions

This is high-quality code that:

  • Solves a real problem elegantly
  • Maintains backward compatibility
  • Follows project conventions
  • Has good error handling

Action Items (non-blocking):

  1. Consider increasing hash length to 12 chars for safety
  2. Add heterogeneous range unit test in this repo
  3. Document client version compatibility
  4. Add benchmark for large heterogeneous ranges

Great work on this feature! The implementation is clean, the wire format is sensible, and the optimization strategy (homogeneous fast path) shows good engineering judgment.


🤖 Reviewed with Claude Code

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a critical rendering bug where range items with different conditional branches (e.g., different form input types) were all rendered using only the first item's static HTML. The solution introduces hash-based deduplication of per-item statics through a new StaticsMap field, maintaining backward compatibility for homogeneous ranges.

Key changes:

  • Added StaticsMap to RangeData for storing unique statics variants by hash key
  • Introduced hashStatics() function using SHA-256 to deduplicate statics across range items
  • Modified range processing to detect homogeneous vs heterogeneous ranges and use appropriate wire format

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.

File Description
internal/parse/range.go Added hash-based statics deduplication logic, new buildRangeTreeWithStatics() function, and extractItemDynamicsWithStaticsKey() for heterogeneous ranges
internal/build/types.go Extended RangeData with StaticsMap field, updated JSON marshaling/unmarshaling to handle "sm" key, added cloning support for statics map
tree_test.go Updated test expectation to include statics field in range data serialization

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/parse/range.go Outdated
Comment on lines +14 to +20
func hashStatics(statics []string) string {
h := sha256.New()
for _, s := range statics {
h.Write([]byte(s))
h.Write([]byte{0}) // separator
}
return hex.EncodeToString(h.Sum(nil))[:8] // 8 chars is enough for dedup
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 8-character hash prefix may not provide sufficient uniqueness for deduplication in large ranges. With 8 hex characters (32 bits), the birthday paradox suggests collisions become likely after ~65,000 unique statics variants. Consider using at least 12-16 characters (48-64 bits) to provide better collision resistance, especially since the hash is used as a key in the StaticsMap which could lead to incorrect rendering if collisions occur.

Suggested change
func hashStatics(statics []string) string {
h := sha256.New()
for _, s := range statics {
h.Write([]byte(s))
h.Write([]byte{0}) // separator
}
return hex.EncodeToString(h.Sum(nil))[:8] // 8 chars is enough for dedup
const staticsHashPrefixLen = 16 // 16 hex chars (64 bits) for low collision risk
func hashStatics(statics []string) string {
h := sha256.New()
for _, s := range statics {
h.Write([]byte(s))
h.Write([]byte{0}) // separator
}
return hex.EncodeToString(h.Sum(nil))[:staticsHashPrefixLen]

Copilot uses AI. Check for mistakes.
Comment thread internal/parse/range.go Outdated
Comment on lines +255 to +256
// Use first item's statics as default (for backward compat)
rangeTree.Statics = items[0].statics
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the heterogeneous case, both Range.Statics and Range.StaticsMap are set. Setting Range.Statics to items[0].statics while also providing StaticsMap creates ambiguity about which should be used. According to line 260's comment "Not used when StaticsMap is present", the Statics field should likely be set to nil in the heterogeneous case to make it clear that StaticsMap is the source of truth.

Suggested change
// Use first item's statics as default (for backward compat)
rangeTree.Statics = items[0].statics
// In the heterogeneous case, StaticsMap is the source of truth.
// Leave TreeNode.Statics nil to avoid ambiguity.
rangeTree.Statics = nil

Copilot uses AI. Check for mistakes.
Comment thread internal/build/types.go Outdated
Comment on lines +340 to +341
// Include statics for range items (homogeneous case)
if len(tn.Range.Statics) > 0 {
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the MarshalJSON method, when Range.Statics is set for a heterogeneous range (line 256 in range.go), it will overwrite the node's own Statics field (line 323-325) since both set result["s"]. This means the heterogeneous range will serialize with the first item's statics in the "s" field alongside "sm", which is inconsistent with the intent expressed in line 260's comment that Statics is "Not used when StaticsMap is present".

Suggested change
// Include statics for range items (homogeneous case)
if len(tn.Range.Statics) > 0 {
// Include statics for range items (homogeneous case).
// When StaticsMap is present (heterogeneous case), Statics is not used.
if len(tn.Range.Statics) > 0 && len(tn.Range.StaticsMap) == 0 {

Copilot uses AI. Check for mistakes.
Comment thread internal/build/types.go
Comment on lines +407 to +431
case "sm":
// Parse statics map for heterogeneous ranges
if smRaw, ok := value.(map[string]interface{}); ok {
if tn.Range == nil {
tn.Range = &RangeData{}
}
tn.Range.StaticsMap = make(map[string][]string)
for hash, staticsRaw := range smRaw {
if statics, ok := staticsRaw.([]interface{}); ok {
staticsStrs := make([]string, len(statics))
for i, s := range statics {
if str, ok := s.(string); ok {
staticsStrs[i] = str
} else {
return fmt.Errorf("invalid statics map entry at %s[%d]: expected string, got %T", hash, i, s)
}
}
tn.Range.StaticsMap[hash] = staticsStrs
} else {
return fmt.Errorf("invalid statics map entry at %s: expected array, got %T", hash, staticsRaw)
}
}
} else {
return fmt.Errorf("invalid statics map: expected object, got %T", value)
}
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UnmarshalJSON method processes the "s" key independently from range data. When both "d" (range items) and "s" (statics) are present with "sm" (statics map), the statics will be set on tn.Statics rather than tn.Range.Statics. However, in the heterogeneous case, MarshalJSON writes Range.Statics to the "s" field (line 342). This creates an asymmetry that could cause the Range.Statics field to be lost during round-trip serialization.

Copilot uses AI. Check for mistakes.
Comment thread internal/parse/range.go
Comment on lines +139 to +143
// Collect item with its statics
itemsWithStatics = append(itemsWithStatics, rangeItemWithStatics{
tree: itemTree,
statics: itemTree.Statics,
hash: hashStatics(itemTree.Statics),
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hash is computed for every range item even when the range is homogeneous (all items have identical statics). This adds unnecessary cryptographic hashing overhead. Consider computing hashes lazily or only when heterogeneity is detected after comparing the first two items' statics arrays directly.

Copilot uses AI. Check for mistakes.
Comment thread internal/parse/range.go
Comment on lines +179 to +183
// Collect item with its statics
itemsWithStatics = append(itemsWithStatics, rangeItemWithStatics{
tree: itemTree,
statics: itemTree.Statics,
hash: hashStatics(itemTree.Statics),
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hash is computed for every range item even when the range is homogeneous (all items have identical statics). This adds unnecessary cryptographic hashing overhead. Consider computing hashes lazily or only when heterogeneity is detected after comparing the first two items' statics arrays directly.

Copilot uses AI. Check for mistakes.
Comment thread internal/build/types.go Outdated
Comment on lines +42 to +43
// Used when all items share the same statics (homogeneous ranges).
// When items have different statics, StaticsMap is used instead.
Copy link

Copilot AI Dec 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment states "Statics are the static HTML parts for rendering range items" but doesn't clearly document the semantic change introduced by StaticsMap. When StaticsMap is present, this field's usage changes from being the authoritative source to being "for backward compat" (as noted in line 255). This should be clarified in the comment to avoid confusion about when to use Statics vs StaticsMap.

Suggested change
// Used when all items share the same statics (homogeneous ranges).
// When items have different statics, StaticsMap is used instead.
// When StaticsMap is not present, this slice is the authoritative source
// of statics and is used when all items share the same statics
// (homogeneous ranges).
//
// When StaticsMap is present, consumers should treat Statics as a
// backward-compatibility field and prefer StaticsMap for determining
// the statics used by each item.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Dec 21, 2025

✅ Performance Benchmark Results

Status: No significant regressions detected

Benchmark Comparison
                                        │ testdata/benchmarks/baseline.txt │  current-bench.txt   │
                                        │              sec/op              │    sec/op      vs base   │
E2ERangeOperations/add-items-8                                66.13µ ± ∞ ¹
E2ERangeOperations/remove-items-8                             32.82µ ± ∞ ¹
E2ERangeOperations/reorder-items-8                            43.52µ ± ∞ ¹
E2ERangeOperations/update-items-8                             59.12µ ± ∞ ¹
SpecificationCompliance-8                                     23.19µ ± ∞ ¹
TemplateExecuteUpdates/no-changes-8                           19.70µ ± ∞ ¹
TemplateExecuteUpdates/small-update-8                         18.58µ ± ∞ ¹
TemplateExecuteUpdates/large-update-8                         64.83µ ± ∞ ¹
TemplateConcurrent/1-8                                        22.86µ ± ∞ ¹
TemplateConcurrent/10-8                                       21.79µ ± ∞ ¹
TemplateConcurrent/100-8                                      24.05µ ± ∞ ¹
Fingerprint_Small_Old-8                                       6.294µ ± ∞ ¹
Fingerprint_Small_New-8                                       28.23µ ± ∞ ¹
Fingerprint_Medium_Old-8                                      61.05µ ± ∞ ¹
Fingerprint_Medium_New-8                                      256.2µ ± ∞ ¹
Fingerprint_Large_Old-8                                       647.3µ ± ∞ ¹
Fingerprint_Large_New-8                                       2.694m ± ∞ ¹
Fingerprint_DeepNested_Old-8                                  63.65µ ± ∞ ¹
Fingerprint_DeepNested_New-8                                  250.4µ ± ∞ ¹
Fingerprint_Range100_Old-8                                    71.70µ ± ∞ ¹
Fingerprint_Range100_New-8                                    242.4µ ± ∞ ¹
Fingerprint_Range1000_Old-8                                   682.6µ ± ∞ ¹
Fingerprint_Range1000_New-8                                   2.371m ± ∞ ¹
Fingerprint_Allocations_Old-8                                 62.43µ ± ∞ ¹
Fingerprint_Allocations_New-8                                 255.9µ ± ∞ ¹
TreeNodeCreation/flat-8                                       573.8n ± ∞ ¹
TreeNodeCreation/nested-small-8                               4.626µ ± ∞ ¹
TreeNodeCreation/nested-medium-8                              13.90µ ± ∞ ¹
TreeNodeCreation/nested-large-8                               41.63µ ± ∞ ¹
TreeNodeMarshalJSON/flat-8                                    4.096µ ± ∞ ¹
TreeNodeMarshalJSON/nested-small-8                            47.46µ ± ∞ ¹
TreeNodeMarshalJSON/nested-medium-8                           172.2µ ± ∞ ¹
WrapperInjection/full-html-8                                  2.982µ ± ∞ ¹
WrapperInjection/fragment-8                                   1.854µ ± ∞ ¹
ExtractWrapperContent-8                                       1.771µ ± ∞ ¹
ContextOperations/with-statics-8                              3.685µ ± ∞ ¹
ContextOperations/without-statics-8                           4.571µ ± ∞ ¹
TreeNodeClone/flat-8                                          382.7n ± ∞ ¹
TreeNodeClone/nested-small-8                                  3.805µ ± ∞ ¹
TreeNodeClone/nested-medium-8                                 11.57µ ± ∞ ¹
TreeNodeToMap/flat-8                                          688.6n ± ∞ ¹
TreeNodeToMap/nested-small-8                                  5.170µ ± ∞ ¹
TreeNodeToMap/nested-medium-8                                 15.42µ ± ∞ ¹
GenerateRandomID-8                                            101.6n ± ∞ ¹
ExecuteTemplateWithContext_Struct-8                           1.837µ ± ∞ ¹
ExecuteTemplateWithContext_Map-8                              2.043µ ± ∞ ¹
CompareTreesNoChanges-8                                       440.3n ± ∞ ¹
CompareTreesSmallChange-8                                     165.5n ± ∞ ¹
CompareTreesLargeChange/10-8                                  956.7n ± ∞ ¹
CompareTreesLargeChange/100-8                                 8.356µ ± ∞ ¹
CompareTreesLargeChange/1000-8                                109.3µ ± ∞ ¹
RangeDiffUpdate-8                                             4.685µ ± ∞ ¹
RangeDiffInsert-8                                             4.831µ ± ∞ ¹
RangeDiffRemove-8                                             4.695µ ± ∞ ¹
PrepareTreeForClient/with-statics-8                           2.061n ± ∞ ¹
PrepareTreeForClient/without-statics-8                        2.552µ ± ∞ ¹
Parse/simple-8                                                1.932µ ± ∞ ¹
Parse/conditional-8                                           2.898µ ± ∞ ¹
Parse/range-8                                                 2.938µ ± ∞ ¹
Parse/nested-8                                                3.890µ ± ∞ ¹
Parse/complex-8                                               6.410µ ± ∞ ¹
BuildTree/simple-8                                            6.167µ ± ∞ ¹
BuildTree/conditional-true-8                                  19.36µ ± ∞ ¹
BuildTree/conditional-false-8                                 5.942µ ± ∞ ¹
BuildTree/range-small-8                                       20.66µ ± ∞ ¹
BuildTreeScale/small-10-8                                     69.43µ ± ∞ ¹
BuildTreeScale/medium-100-8                                   672.8µ ± ∞ ¹
BuildTreeScale/large-1000-8                                   8.267m ± ∞ ¹
NodeRender-8                                                  74.22n ± ∞ ¹
TreeToHTML/simple-8                                           119.4n ± ∞ ¹
TreeToHTML/nested-8                                           218.1n ± ∞ ¹
TreeToHTML/with-ranges-8                                      187.3n ± ∞ ¹
TreeToHTMLScale/small-10-8                                    783.3n ± ∞ ¹
TreeToHTMLScale/medium-100-8                                  6.283µ ± ∞ ¹
TreeToHTMLScale/large-1000-8                                  63.07µ ± ∞ ¹
IsVoidElement-8                                               86.31n ± ∞ ¹
NodeRenderComplex-8                                           200.4n ± ∞ ¹
MinifyHTML-8                                                  975.0n ± ∞ ¹
ParseActionFromHTTP-8                                         1.710µ ± ∞ ¹
ParseActionFromWebSocket-8                                    675.1n ± ∞ ¹
PrepareUpdate/without-errors-8                               0.3124n ± ∞ ¹
PrepareUpdate/with-errors-8                                   15.33n ± ∞ ¹
SerializeUpdate-8                                             856.2n ± ∞ ¹
PrepareAndSerialize/simple-update-8                           923.9n ± ∞ ¹
PrepareAndSerialize/with-metadata-8                           1.178µ ± ∞ ¹
ParseActionScale/small-http-8                                 1.718µ ± ∞ ¹
ParseActionScale/small-ws-8                                   649.3n ± ∞ ¹
ParseActionScale/medium-http-8                                2.850µ ± ∞ ¹
ParseActionScale/medium-ws-8                                  1.289µ ± ∞ ¹
ParseActionScale/large-http-8                                 3.719µ ± ∞ ¹
ParseActionScale/large-ws-8                                   2.339µ ± ∞ ¹
SerializeUpdateScale/simple-8                                 874.3n ± ∞ ¹
SerializeUpdateScale/nested-8                                 1.919µ ± ∞ ¹
SerializeUpdateScale/multiple-fields-8                        1.476µ ± ∞ ¹
DispatchWithState_Cached-4                                                    452.4n ± ∞ ¹
E2ERangeOperations/add-items-4                                                120.4µ ± ∞ ¹
E2ERangeOperations/remove-items-4                                             66.22µ ± ∞ ¹
E2ERangeOperations/reorder-items-4                                            84.36µ ± ∞ ¹
E2ERangeOperations/update-items-4                                             84.70µ ± ∞ ¹
SpecificationCompliance-4                                                     32.60µ ± ∞ ¹
TemplateExecuteUpdates/no-changes-4                                           34.34µ ± ∞ ¹
TemplateExecuteUpdates/small-update-4                                         34.43µ ± ∞ ¹
TemplateExecuteUpdates/large-update-4                                         115.9µ ± ∞ ¹
TemplateConcurrent/goroutines-1-4                                             44.20µ ± ∞ ¹
TemplateConcurrent/goroutines-10-4                                            50.73µ ± ∞ ¹
TemplateConcurrent/goroutines-100-4                                           51.53µ ± ∞ ¹
Fingerprint_Small_Old-4                                                       9.495µ ± ∞ ¹
Fingerprint_Small_New-4                                                       43.56µ ± ∞ ¹
Fingerprint_Medium_Old-4                                                      94.94µ ± ∞ ¹
Fingerprint_Medium_New-4                                                      417.5µ ± ∞ ¹
Fingerprint_Large_Old-4                                                       1.028m ± ∞ ¹
Fingerprint_Large_New-4                                                       4.328m ± ∞ ¹
Fingerprint_DeepNested_Old-4                                                  94.67µ ± ∞ ¹
Fingerprint_DeepNested_New-4                                                  378.4µ ± ∞ ¹
Fingerprint_Range100_Old-4                                                    109.1µ ± ∞ ¹
Fingerprint_Range100_New-4                                                    380.9µ ± ∞ ¹
Fingerprint_Range1000_Old-4                                                   1.088m ± ∞ ¹
Fingerprint_Range1000_New-4                                                   3.710m ± ∞ ¹
Fingerprint_Allocations_Old-4                                                 94.85µ ± ∞ ¹
Fingerprint_Allocations_New-4                                                 414.9µ ± ∞ ¹
TreeNodeCreation/flat-4                                                       1.024µ ± ∞ ¹
TreeNodeCreation/nested-small-4                                               7.762µ ± ∞ ¹
TreeNodeCreation/nested-medium-4                                              23.73µ ± ∞ ¹
TreeNodeCreation/nested-large-4                                               72.16µ ± ∞ ¹
TreeNodeMarshalJSON/flat-4                                                    6.706µ ± ∞ ¹
TreeNodeMarshalJSON/nested-small-4                                            69.71µ ± ∞ ¹
TreeNodeMarshalJSON/nested-medium-4                                           250.3µ ± ∞ ¹
WrapperInjection/full-html-4                                                  5.509µ ± ∞ ¹
WrapperInjection/fragment-4                                                   3.547µ ± ∞ ¹
ExtractWrapperContent-4                                                       3.417µ ± ∞ ¹
ContextOperations/with-statics-4                                              5.589µ ± ∞ ¹
ContextOperations/without-statics-4                                           5.583µ ± ∞ ¹
TreeNodeClone/flat-4                                                          653.4n ± ∞ ¹
TreeNodeClone/nested-small-4                                                  5.571µ ± ∞ ¹
TreeNodeClone/nested-medium-4                                                 17.29µ ± ∞ ¹
TreeNodeToMap/flat-4                                                          1.027µ ± ∞ ¹
TreeNodeToMap/nested-small-4                                                  7.559µ ± ∞ ¹
TreeNodeToMap/nested-medium-4                                                 22.99µ ± ∞ ¹
GenerateRandomID-4                                                            91.52n ± ∞ ¹
ExecuteTemplateWithContext_Struct-4                                           3.606µ ± ∞ ¹
ExecuteTemplateWithContext_Map-4                                              3.927µ ± ∞ ¹
CompareTreesNoChanges-4                                                       647.2n ± ∞ ¹
CompareTreesSmallChange-4                                                     242.2n ± ∞ ¹
CompareTreesLargeChange/10-4                                                  1.405µ ± ∞ ¹
CompareTreesLargeChange/100-4                                                 13.81µ ± ∞ ¹
CompareTreesLargeChange/1000-4                                                182.4µ ± ∞ ¹
RangeDiffUpdate-4                                                             7.422µ ± ∞ ¹
RangeDiffInsert-4                                                             7.454µ ± ∞ ¹
RangeDiffRemove-4                                                             7.404µ ± ∞ ¹
PrepareTreeForClient/with-statics-4                                           2.314n ± ∞ ¹
PrepareTreeForClient/without-statics-4                                        4.416µ ± ∞ ¹
Parse/simple-4                                                                3.095µ ± ∞ ¹
Parse/conditional-4                                                           5.356µ ± ∞ ¹
Parse/range-4                                                                 4.699µ ± ∞ ¹
Parse/nested-4                                                                6.016µ ± ∞ ¹
Parse/complex-4                                                               11.43µ ± ∞ ¹
BuildTree/simple-4                                                            11.91µ ± ∞ ¹
BuildTree/conditional-true-4                                                  23.18µ ± ∞ ¹
BuildTree/conditional-false-4                                                 11.25µ ± ∞ ¹
BuildTree/range-small-4                                                       41.13µ ± ∞ ¹
BuildTreeScale/small-10-4                                                     131.4µ ± ∞ ¹
BuildTreeScale/medium-100-4                                                   1.309m ± ∞ ¹
BuildTreeScale/large-1000-4                                                   14.77m ± ∞ ¹
NodeRender-4                                                                  108.3n ± ∞ ¹
TreeToHTML/simple-4                                                           196.3n ± ∞ ¹
TreeToHTML/nested-4                                                           335.2n ± ∞ ¹
TreeToHTML/with-ranges-4                                                      310.9n ± ∞ ¹
TreeToHTMLScale/small-10-4                                                    1.257µ ± ∞ ¹
TreeToHTMLScale/medium-100-4                                                  10.79µ ± ∞ ¹
TreeToHTMLScale/large-1000-4                                                  110.9µ ± ∞ ¹
IsVoidElement-4                                                               105.3n ± ∞ ¹
NodeRenderComplex-4                                                           310.5n ± ∞ ¹
MinifyHTML-4                                                                  1.667µ ± ∞ ¹
ParseActionFromHTTP-4                                                         3.678µ ± ∞ ¹
ParseActionFromWebSocket-4                                                    1.311µ ± ∞ ¹
PrepareUpdate/without-errors-4                                               0.3119n ± ∞ ¹
PrepareUpdate/with-errors-4                                                   26.27n ± ∞ ¹
SerializeUpdate-4                                                             1.598µ ± ∞ ¹
PrepareAndSerialize/simple-update-4                                           1.642µ ± ∞ ¹
PrepareAndSerialize/with-metadata-4                                           2.071µ ± ∞ ¹
ParseActionScale/small-http-4                                                 3.632µ ± ∞ ¹
ParseActionScale/small-ws-4                                                   1.253µ ± ∞ ¹
ParseActionScale/medium-http-4                                                4.680µ ± ∞ ¹
ParseActionScale/medium-ws-4                                                  2.263µ ± ∞ ¹
ParseActionScale/large-http-4                                                 6.933µ ± ∞ ¹
ParseActionScale/large-ws-4                                                   4.439µ ± ∞ ¹
SerializeUpdateScale/simple-4                                                 1.613µ ± ∞ ¹
SerializeUpdateScale/nested-4                                                 3.341µ ± ∞ ¹
SerializeUpdateScale/multiple-fields-4                                        2.595µ ± ∞ ¹
AsyncSendThroughput-4                                                         36.00n ± ∞ ¹
ConcurrentConnections/000_connections-4                                       234.0n ± ∞ ¹
ConcurrentConnections/100_connections-4                                       2.435µ ± ∞ ¹
ConcurrentConnections/:00_connections-4                                       266.3µ ± ∞ ¹
RegisterUnregister-4                                                          2.559µ ± ∞ ¹
ConcurrentSend-4                                                              18.75n ± ∞ ¹
GetByGroup-4                                                                  428.4n ± ∞ ¹
CloseConnection-4                                                             4.826µ ± ∞ ¹
MemoryUsage-4                                                                 67.95µ ± ∞ ¹
BroadcastToGroup-4                                                            23.90µ ± ∞ ¹
BufferSizes/0_size-4                                                          22.32n ± ∞ ¹
BufferSizes/0_size#01-4                                                       22.24n ± ∞ ¹
BufferSizes/1_size-4                                                          22.25n ± ∞ ¹
BufferSizes/5_size-4                                                          22.30n ± ∞ ¹
BufferSizes/:_size-4                                                          25.49n ± ∞ ¹
ConcurrentRegistrations-4                                                     2.546µ ± ∞ ¹
GetByGroupExcept-4                                                            534.4n ± ∞ ¹
geomean                                                       5.754µ          5.912µ        ? ² ³
¹ need >= 6 samples for confidence interval at level 0.95
² benchmark set differs from baseline; geomeans may not be comparable
³ ratios must be >0 to compute geomean

                                        │ testdata/benchmarks/baseline.txt │   current-bench.txt    │
                                        │               B/op               │     B/op       vs base │
E2ERangeOperations/add-items-8                               80.55Ki ± ∞ ¹
E2ERangeOperations/remove-items-8                            43.62Ki ± ∞ ¹
E2ERangeOperations/reorder-items-8                           56.91Ki ± ∞ ¹
E2ERangeOperations/update-items-8                            56.91Ki ± ∞ ¹
SpecificationCompliance-8                                    27.32Ki ± ∞ ¹
TemplateExecuteUpdates/no-changes-8                          27.39Ki ± ∞ ¹
TemplateExecuteUpdates/small-update-8                        27.39Ki ± ∞ ¹
TemplateExecuteUpdates/large-update-8                        76.23Ki ± ∞ ¹
TemplateConcurrent/1-8                                       28.32Ki ± ∞ ¹
TemplateConcurrent/10-8                                      28.33Ki ± ∞ ¹
TemplateConcurrent/100-8                                     28.33Ki ± ∞ ¹
Fingerprint_Small_Old-8                                      2.688Ki ± ∞ ¹
Fingerprint_Small_New-8                                      13.36Ki ± ∞ ¹
Fingerprint_Medium_Old-8                                     24.85Ki ± ∞ ¹
Fingerprint_Medium_New-8                                     125.3Ki ± ∞ ¹
Fingerprint_Large_Old-8                                      237.7Ki ± ∞ ¹
Fingerprint_Large_New-8                                      1.352Mi ± ∞ ¹
Fingerprint_DeepNested_Old-8                                 30.53Ki ± ∞ ¹
Fingerprint_DeepNested_New-8                                 124.8Ki ± ∞ ¹
Fingerprint_Range100_Old-8                                   36.38Ki ± ∞ ¹
Fingerprint_Range100_New-8                                   154.6Ki ± ∞ ¹
Fingerprint_Range1000_Old-8                                  372.4Ki ± ∞ ¹
Fingerprint_Range1000_New-8                                  1.504Mi ± ∞ ¹
Fingerprint_Allocations_Old-8                                24.85Ki ± ∞ ¹
Fingerprint_Allocations_New-8                                125.3Ki ± ∞ ¹
TreeNodeCreation/flat-8                                      1.188Ki ± ∞ ¹
TreeNodeCreation/nested-small-8                              9.691Ki ± ∞ ¹
TreeNodeCreation/nested-medium-8                             29.53Ki ± ∞ ¹
TreeNodeCreation/nested-large-8                              89.04Ki ± ∞ ¹
TreeNodeMarshalJSON/flat-8                                   3.472Ki ± ∞ ¹
TreeNodeMarshalJSON/nested-small-8                           27.29Ki ± ∞ ¹
TreeNodeMarshalJSON/nested-medium-8                          89.07Ki ± ∞ ¹
WrapperInjection/full-html-8                                 6.930Ki ± ∞ ¹
WrapperInjection/fragment-8                                  6.266Ki ± ∞ ¹
ExtractWrapperContent-8                                      5.883Ki ± ∞ ¹
ContextOperations/with-statics-8                             8.422Ki ± ∞ ¹
ContextOperations/without-statics-8                          8.422Ki ± ∞ ¹
TreeNodeClone/flat-8                                           976.0 ± ∞ ¹
TreeNodeClone/nested-small-8                                 8.422Ki ± ∞ ¹
TreeNodeClone/nested-medium-8                                25.72Ki ± ∞ ¹
TreeNodeToMap/flat-8                                         2.109Ki ± ∞ ¹
TreeNodeToMap/nested-small-8                                 14.06Ki ± ∞ ¹
TreeNodeToMap/nested-medium-8                                42.54Ki ± ∞ ¹
GenerateRandomID-8                                             24.00 ± ∞ ¹
ExecuteTemplateWithContext_Struct-8                          1.002Ki ± ∞ ¹
ExecuteTemplateWithContext_Map-8                             1.174Ki ± ∞ ¹
CompareTreesNoChanges-8                                        112.0 ± ∞ ¹
CompareTreesSmallChange-8                                      400.0 ± ∞ ¹
CompareTreesLargeChange/10-8                                  1016.0 ± ∞ ¹
CompareTreesLargeChange/100-8                                9.211Ki ± ∞ ¹
CompareTreesLargeChange/1000-8                               156.6Ki ± ∞ ¹
RangeDiffUpdate-8                                            18.59Ki ± ∞ ¹
RangeDiffInsert-8                                            18.59Ki ± ∞ ¹
RangeDiffRemove-8                                            18.59Ki ± ∞ ¹
PrepareTreeForClient/with-statics-8                            0.000 ± ∞ ¹
PrepareTreeForClient/without-statics-8                       4.898Ki ± ∞ ¹
Parse/simple-8                                               3.656Ki ± ∞ ¹
Parse/conditional-8                                          4.398Ki ± ∞ ¹
Parse/range-8                                                4.234Ki ± ∞ ¹
Parse/nested-8                                               4.609Ki ± ∞ ¹
Parse/complex-8                                              6.906Ki ± ∞ ¹
BuildTree/simple-8                                           10.93Ki ± ∞ ¹
BuildTree/conditional-true-8                                 21.32Ki ± ∞ ¹
BuildTree/conditional-false-8                                10.67Ki ± ∞ ¹
BuildTree/range-small-8                                      34.97Ki ± ∞ ¹
BuildTreeScale/small-10-8                                    113.8Ki ± ∞ ¹
BuildTreeScale/medium-100-8                                  1.101Mi ± ∞ ¹
BuildTreeScale/large-1000-8                                  11.02Mi ± ∞ ¹
NodeRender-8                                                   56.00 ± ∞ ¹
TreeToHTML/simple-8                                            64.00 ± ∞ ¹
TreeToHTML/nested-8                                            144.0 ± ∞ ¹
TreeToHTML/with-ranges-8                                       72.00 ± ∞ ¹
TreeToHTMLScale/small-10-8                                     544.0 ± ∞ ¹
TreeToHTMLScale/medium-100-8                                 3.634Ki ± ∞ ¹
TreeToHTMLScale/large-1000-8                                 65.43Ki ± ∞ ¹
IsVoidElement-8                                                0.000 ± ∞ ¹
NodeRenderComplex-8                                            248.0 ± ∞ ¹
MinifyHTML-8                                                 1.641Ki ± ∞ ¹
ParseActionFromHTTP-8                                        6.799Ki ± ∞ ¹
ParseActionFromWebSocket-8                                     656.0 ± ∞ ¹
PrepareUpdate/without-errors-8                                 0.000 ± ∞ ¹
PrepareUpdate/with-errors-8                                    32.00 ± ∞ ¹
SerializeUpdate-8                                              648.0 ± ∞ ¹
PrepareAndSerialize/simple-update-8                            672.0 ± ∞ ¹
PrepareAndSerialize/with-metadata-8                            848.0 ± ∞ ¹
ParseActionScale/small-http-8                                6.791Ki ± ∞ ¹
ParseActionScale/small-ws-8                                    648.0 ± ∞ ¹
ParseActionScale/medium-http-8                               6.955Ki ± ∞ ¹
ParseActionScale/medium-ws-8                                   760.0 ± ∞ ¹
ParseActionScale/large-http-8                                7.885Ki ± ∞ ¹
ParseActionScale/large-ws-8                                  1.578Ki ± ∞ ¹
SerializeUpdateScale/simple-8                                  648.0 ± ∞ ¹
SerializeUpdateScale/nested-8                                1.282Ki ± ∞ ¹
SerializeUpdateScale/multiple-fields-8                         968.0 ± ∞ ¹
DispatchWithState_Cached-4                                                     80.00 ± ∞ ¹
E2ERangeOperations/add-items-4                                               81.31Ki ± ∞ ¹
E2ERangeOperations/remove-items-4                                            45.39Ki ± ∞ ¹
E2ERangeOperations/reorder-items-4                                           57.41Ki ± ∞ ¹
E2ERangeOperations/update-items-4                                            57.41Ki ± ∞ ¹
SpecificationCompliance-4                                                    27.33Ki ± ∞ ¹
TemplateExecuteUpdates/no-changes-4                                          27.39Ki ± ∞ ¹
TemplateExecuteUpdates/small-update-4                                        27.39Ki ± ∞ ¹
TemplateExecuteUpdates/large-update-4                                        76.19Ki ± ∞ ¹
TemplateConcurrent/goroutines-1-4                                            28.35Ki ± ∞ ¹
TemplateConcurrent/goroutines-10-4                                           28.36Ki ± ∞ ¹
TemplateConcurrent/goroutines-100-4                                          28.36Ki ± ∞ ¹
Fingerprint_Small_Old-4                                                      2.688Ki ± ∞ ¹
Fingerprint_Small_New-4                                                      13.34Ki ± ∞ ¹
Fingerprint_Medium_Old-4                                                     24.84Ki ± ∞ ¹
Fingerprint_Medium_New-4                                                     125.1Ki ± ∞ ¹
Fingerprint_Large_Old-4                                                      237.7Ki ± ∞ ¹
Fingerprint_Large_New-4                                                      1.345Mi ± ∞ ¹
Fingerprint_DeepNested_Old-4                                                 30.52Ki ± ∞ ¹
Fingerprint_DeepNested_New-4                                                 124.6Ki ± ∞ ¹
Fingerprint_Range100_Old-4                                                   36.36Ki ± ∞ ¹
Fingerprint_Range100_New-4                                                   154.3Ki ± ∞ ¹
Fingerprint_Range1000_Old-4                                                  369.4Ki ± ∞ ¹
Fingerprint_Range1000_New-4                                                  1.496Mi ± ∞ ¹
Fingerprint_Allocations_Old-4                                                24.84Ki ± ∞ ¹
Fingerprint_Allocations_New-4                                                125.1Ki ± ∞ ¹
TreeNodeCreation/flat-4                                                      1.188Ki ± ∞ ¹
TreeNodeCreation/nested-small-4                                              9.689Ki ± ∞ ¹
TreeNodeCreation/nested-medium-4                                             29.52Ki ± ∞ ¹
TreeNodeCreation/nested-large-4                                              89.02Ki ± ∞ ¹
TreeNodeMarshalJSON/flat-4                                                   3.471Ki ± ∞ ¹
TreeNodeMarshalJSON/nested-small-4                                           27.27Ki ± ∞ ¹
TreeNodeMarshalJSON/nested-medium-4                                          88.96Ki ± ∞ ¹
WrapperInjection/full-html-4                                                 6.930Ki ± ∞ ¹
WrapperInjection/fragment-4                                                  6.266Ki ± ∞ ¹
ExtractWrapperContent-4                                                      5.883Ki ± ∞ ¹
ContextOperations/with-statics-4                                             8.422Ki ± ∞ ¹
ContextOperations/without-statics-4                                          8.422Ki ± ∞ ¹
TreeNodeClone/flat-4                                                           976.0 ± ∞ ¹
TreeNodeClone/nested-small-4                                                 8.422Ki ± ∞ ¹
TreeNodeClone/nested-medium-4                                                25.72Ki ± ∞ ¹
TreeNodeToMap/flat-4                                                         2.109Ki ± ∞ ¹
TreeNodeToMap/nested-small-4                                                 14.06Ki ± ∞ ¹
TreeNodeToMap/nested-medium-4                                                42.54Ki ± ∞ ¹
GenerateRandomID-4                                                             24.00 ± ∞ ¹
ExecuteTemplateWithContext_Struct-4                                          1.018Ki ± ∞ ¹
ExecuteTemplateWithContext_Map-4                                             1.189Ki ± ∞ ¹
CompareTreesNoChanges-4                                                        112.0 ± ∞ ¹
CompareTreesSmallChange-4                                                      400.0 ± ∞ ¹
CompareTreesLargeChange/10-4                                                  1016.0 ± ∞ ¹
CompareTreesLargeChange/100-4                                                9.211Ki ± ∞ ¹
CompareTreesLargeChange/1000-4                                               156.6Ki ± ∞ ¹
RangeDiffUpdate-4                                                            18.59Ki ± ∞ ¹
RangeDiffInsert-4                                                            18.59Ki ± ∞ ¹
RangeDiffRemove-4                                                            18.59Ki ± ∞ ¹
PrepareTreeForClient/with-statics-4                                            0.000 ± ∞ ¹
PrepareTreeForClient/without-statics-4                                       4.898Ki ± ∞ ¹
Parse/simple-4                                                               3.656Ki ± ∞ ¹
Parse/conditional-4                                                          4.398Ki ± ∞ ¹
Parse/range-4                                                                4.234Ki ± ∞ ¹
Parse/nested-4                                                               4.609Ki ± ∞ ¹
Parse/complex-4                                                              6.906Ki ± ∞ ¹
BuildTree/simple-4                                                           10.93Ki ± ∞ ¹
BuildTree/conditional-true-4                                                 21.31Ki ± ∞ ¹
BuildTree/conditional-false-4                                                10.66Ki ± ∞ ¹
BuildTree/range-small-4                                                      35.51Ki ± ∞ ¹
BuildTreeScale/small-10-4                                                    115.4Ki ± ∞ ¹
BuildTreeScale/medium-100-4                                                  1.118Mi ± ∞ ¹
BuildTreeScale/large-1000-4                                                  11.19Mi ± ∞ ¹
NodeRender-4                                                                   56.00 ± ∞ ¹
TreeToHTML/simple-4                                                            64.00 ± ∞ ¹
TreeToHTML/nested-4                                                            144.0 ± ∞ ¹
TreeToHTML/with-ranges-4                                                       72.00 ± ∞ ¹
TreeToHTMLScale/small-10-4                                                     544.0 ± ∞ ¹
TreeToHTMLScale/medium-100-4                                                 3.633Ki ± ∞ ¹
TreeToHTMLScale/large-1000-4                                                 65.41Ki ± ∞ ¹
IsVoidElement-4                                                                0.000 ± ∞ ¹
NodeRenderComplex-4                                                            248.0 ± ∞ ¹
MinifyHTML-4                                                                 1.641Ki ± ∞ ¹
ParseActionFromHTTP-4                                                        6.798Ki ± ∞ ¹
ParseActionFromWebSocket-4                                                     656.0 ± ∞ ¹
PrepareUpdate/without-errors-4                                                 0.000 ± ∞ ¹
PrepareUpdate/with-errors-4                                                    32.00 ± ∞ ¹
SerializeUpdate-4                                                              648.0 ± ∞ ¹
PrepareAndSerialize/simple-update-4                                            672.0 ± ∞ ¹
PrepareAndSerialize/with-metadata-4                                            848.0 ± ∞ ¹
ParseActionScale/small-http-4                                                6.790Ki ± ∞ ¹
ParseActionScale/small-ws-4                                                    648.0 ± ∞ ¹
ParseActionScale/medium-http-4                                               6.954Ki ± ∞ ¹
ParseActionScale/medium-ws-4                                                   760.0 ± ∞ ¹
ParseActionScale/large-http-4                                                7.884Ki ± ∞ ¹
ParseActionScale/large-ws-4                                                  1.578Ki ± ∞ ¹
SerializeUpdateScale/simple-4                                                  648.0 ± ∞ ¹
SerializeUpdateScale/nested-4                                                1.281Ki ± ∞ ¹
SerializeUpdateScale/multiple-fields-4                                         968.0 ± ∞ ¹
AsyncSendThroughput-4                                                          24.00 ± ∞ ¹
ConcurrentConnections/000_connections-4                                        40.00 ± ∞ ¹
ConcurrentConnections/100_connections-4                                        422.0 ± ∞ ¹
ConcurrentConnections/:00_connections-4                                      35.16Ki ± ∞ ¹
RegisterUnregister-4                                                         1.182Ki ± ∞ ¹
ConcurrentSend-4                                                               24.00 ± ∞ ¹
GetByGroup-4                                                                   896.0 ± ∞ ¹
CloseConnection-4                                                              251.0 ± ∞ ¹
MemoryUsage-4                                                                95.57Ki ± ∞ ¹
BroadcastToGroup-4                                                           4.000Ki ± ∞ ¹
BufferSizes/0_size-4                                                           4.000 ± ∞ ¹
BufferSizes/0_size#01-4                                                        4.000 ± ∞ ¹
BufferSizes/1_size-4                                                           4.000 ± ∞ ¹
BufferSizes/5_size-4                                                           4.000 ± ∞ ¹
BufferSizes/:_size-4                                                           4.000 ± ∞ ¹
ConcurrentRegistrations-4                                                    1.182Ki ± ∞ ¹
GetByGroupExcept-4                                                             896.0 ± ∞ ¹
geomean                                                                  ²                  ? ³ ² ⁴
¹ need >= 6 samples for confidence interval at level 0.95
² summaries must be >0 to compute geomean
³ benchmark set differs from baseline; geomeans may not be comparable
⁴ ratios must be >0 to compute geomean

                                        │ testdata/benchmarks/baseline.txt │   current-bench.txt   │
                                        │            allocs/op             │  allocs/op    vs base │
E2ERangeOperations/add-items-8                                 756.0 ± ∞ ¹
E2ERangeOperations/remove-items-8                              405.0 ± ∞ ¹
E2ERangeOperations/reorder-items-8                             525.0 ± ∞ ¹
E2ERangeOperations/update-items-8                              525.0 ± ∞ ¹
SpecificationCompliance-8                                      213.0 ± ∞ ¹
TemplateExecuteUpdates/no-changes-8                            221.0 ± ∞ ¹
TemplateExecuteUpdates/small-update-8                          221.0 ± ∞ ¹
TemplateExecuteUpdates/large-update-8                          728.0 ± ∞ ¹
TemplateConcurrent/1-8                                         237.0 ± ∞ ¹
TemplateConcurrent/10-8                                        237.0 ± ∞ ¹
TemplateConcurrent/100-8                                       237.0 ± ∞ ¹
Fingerprint_Small_Old-8                                        71.00 ± ∞ ¹
Fingerprint_Small_New-8                                        386.0 ± ∞ ¹
Fingerprint_Medium_Old-8                                       614.0 ± ∞ ¹
Fingerprint_Medium_New-8                                      3.548k ± ∞ ¹
Fingerprint_Large_Old-8                                       6.017k ± ∞ ¹
Fingerprint_Large_New-8                                       35.07k ± ∞ ¹
Fingerprint_DeepNested_Old-8                                   768.0 ± ∞ ¹
Fingerprint_DeepNested_New-8                                  3.069k ± ∞ ¹
Fingerprint_Range100_Old-8                                    1.012k ± ∞ ¹
Fingerprint_Range100_New-8                                    3.082k ± ∞ ¹
Fingerprint_Range1000_Old-8                                   10.01k ± ∞ ¹
Fingerprint_Range1000_New-8                                   30.09k ± ∞ ¹
Fingerprint_Allocations_Old-8                                  614.0 ± ∞ ¹
Fingerprint_Allocations_New-8                                 3.548k ± ∞ ¹
TreeNodeCreation/flat-8                                        19.00 ± ∞ ¹
TreeNodeCreation/nested-small-8                                133.0 ± ∞ ¹
TreeNodeCreation/nested-medium-8                               403.0 ± ∞ ¹
TreeNodeCreation/nested-large-8                               1.213k ± ∞ ¹
TreeNodeMarshalJSON/flat-8                                     52.00 ± ∞ ¹
TreeNodeMarshalJSON/nested-small-8                             358.0 ± ∞ ¹
TreeNodeMarshalJSON/nested-medium-8                           1.087k ± ∞ ¹
WrapperInjection/full-html-8                                   37.00 ± ∞ ¹
WrapperInjection/fragment-8                                    28.00 ± ∞ ¹
ExtractWrapperContent-8                                        28.00 ± ∞ ¹
ContextOperations/with-statics-8                               106.0 ± ∞ ¹
ContextOperations/without-statics-8                            106.0 ± ∞ ¹
TreeNodeClone/flat-8                                           14.00 ± ∞ ¹
TreeNodeClone/nested-small-8                                   106.0 ± ∞ ¹
TreeNodeClone/nested-medium-8                                  322.0 ± ∞ ¹
TreeNodeToMap/flat-8                                           18.00 ± ∞ ¹
TreeNodeToMap/nested-small-8                                   120.0 ± ∞ ¹
TreeNodeToMap/nested-medium-8                                  363.0 ± ∞ ¹
GenerateRandomID-8                                             1.000 ± ∞ ¹
ExecuteTemplateWithContext_Struct-8                            29.00 ± ∞ ¹
ExecuteTemplateWithContext_Map-8                               36.00 ± ∞ ¹
CompareTreesNoChanges-8                                        2.000 ± ∞ ¹
CompareTreesSmallChange-8                                      3.000 ± ∞ ¹
CompareTreesLargeChange/10-8                                   6.000 ± ∞ ¹
CompareTreesLargeChange/100-8                                  12.00 ± ∞ ¹
CompareTreesLargeChange/1000-8                                 23.00 ± ∞ ¹
RangeDiffUpdate-8                                              36.00 ± ∞ ¹
RangeDiffInsert-8                                              36.00 ± ∞ ¹
RangeDiffRemove-8                                              36.00 ± ∞ ¹
PrepareTreeForClient/with-statics-8                            0.000 ± ∞ ¹
PrepareTreeForClient/without-statics-8                         5.000 ± ∞ ¹
Parse/simple-8                                                 43.00 ± ∞ ¹
Parse/conditional-8                                            64.00 ± ∞ ¹
Parse/range-8                                                  59.00 ± ∞ ¹
Parse/nested-8                                                 69.00 ± ∞ ¹
Parse/complex-8                                                126.0 ± ∞ ¹
BuildTree/simple-8                                             101.0 ± ∞ ¹
BuildTree/conditional-true-8                                   197.0 ± ∞ ¹
BuildTree/conditional-false-8                                  103.0 ± ∞ ¹
BuildTree/range-small-8                                        350.0 ± ∞ ¹
BuildTreeScale/small-10-8                                     1.101k ± ∞ ¹
BuildTreeScale/medium-100-8                                   10.74k ± ∞ ¹
BuildTreeScale/large-1000-8                                   107.1k ± ∞ ¹
NodeRender-8                                                   3.000 ± ∞ ¹
TreeToHTML/simple-8                                            4.000 ± ∞ ¹
TreeToHTML/nested-8                                            7.000 ± ∞ ¹
TreeToHTML/with-ranges-8                                       5.000 ± ∞ ¹
TreeToHTMLScale/small-10-8                                     16.00 ± ∞ ¹
TreeToHTMLScale/medium-100-8                                   109.0 ± ∞ ¹
TreeToHTMLScale/large-1000-8                                  1.017k ± ∞ ¹
IsVoidElement-8                                                0.000 ± ∞ ¹
NodeRenderComplex-8                                            5.000 ± ∞ ¹
MinifyHTML-8                                                   13.00 ± ∞ ¹
ParseActionFromHTTP-8                                          30.00 ± ∞ ¹
ParseActionFromWebSocket-8                                     13.00 ± ∞ ¹
PrepareUpdate/without-errors-8                                 0.000 ± ∞ ¹
PrepareUpdate/with-errors-8                                    1.000 ± ∞ ¹
SerializeUpdate-8                                              10.00 ± ∞ ¹
PrepareAndSerialize/simple-update-8                            11.00 ± ∞ ¹
PrepareAndSerialize/with-metadata-8                            15.00 ± ∞ ¹
ParseActionScale/small-http-8                                  30.00 ± ∞ ¹
ParseActionScale/small-ws-8                                    13.00 ± ∞ ¹
ParseActionScale/medium-http-8                                 40.00 ± ∞ ¹
ParseActionScale/medium-ws-8                                   23.00 ± ∞ ¹
ParseActionScale/large-http-8                                  63.00 ± ∞ ¹
ParseActionScale/large-ws-8                                    46.00 ± ∞ ¹
SerializeUpdateScale/simple-8                                  10.00 ± ∞ ¹
SerializeUpdateScale/nested-8                                  19.00 ± ∞ ¹
SerializeUpdateScale/multiple-fields-8                         16.00 ± ∞ ¹
DispatchWithState_Cached-4                                                    4.000 ± ∞ ¹
E2ERangeOperations/add-items-4                                                769.0 ± ∞ ¹
E2ERangeOperations/remove-items-4                                             413.0 ± ∞ ¹
E2ERangeOperations/reorder-items-4                                            533.0 ± ∞ ¹
E2ERangeOperations/update-items-4                                             533.0 ± ∞ ¹
SpecificationCompliance-4                                                     213.0 ± ∞ ¹
TemplateExecuteUpdates/no-changes-4                                           221.0 ± ∞ ¹
TemplateExecuteUpdates/small-update-4                                         221.0 ± ∞ ¹
TemplateExecuteUpdates/large-update-4                                         728.0 ± ∞ ¹
TemplateConcurrent/goroutines-1-4                                             237.0 ± ∞ ¹
TemplateConcurrent/goroutines-10-4                                            237.0 ± ∞ ¹
TemplateConcurrent/goroutines-100-4                                           237.0 ± ∞ ¹
Fingerprint_Small_Old-4                                                       71.00 ± ∞ ¹
Fingerprint_Small_New-4                                                       386.0 ± ∞ ¹
Fingerprint_Medium_Old-4                                                      614.0 ± ∞ ¹
Fingerprint_Medium_New-4                                                     3.548k ± ∞ ¹
Fingerprint_Large_Old-4                                                      6.017k ± ∞ ¹
Fingerprint_Large_New-4                                                      35.07k ± ∞ ¹
Fingerprint_DeepNested_Old-4                                                  768.0 ± ∞ ¹
Fingerprint_DeepNested_New-4                                                 3.069k ± ∞ ¹
Fingerprint_Range100_Old-4                                                   1.012k ± ∞ ¹
Fingerprint_Range100_New-4                                                   3.082k ± ∞ ¹
Fingerprint_Range1000_Old-4                                                  10.01k ± ∞ ¹
Fingerprint_Range1000_New-4                                                  30.09k ± ∞ ¹
Fingerprint_Allocations_Old-4                                                 614.0 ± ∞ ¹
Fingerprint_Allocations_New-4                                                3.548k ± ∞ ¹
TreeNodeCreation/flat-4                                                       19.00 ± ∞ ¹
TreeNodeCreation/nested-small-4                                               133.0 ± ∞ ¹
TreeNodeCreation/nested-medium-4                                              403.0 ± ∞ ¹
TreeNodeCreation/nested-large-4                                              1.213k ± ∞ ¹
TreeNodeMarshalJSON/flat-4                                                    52.00 ± ∞ ¹
TreeNodeMarshalJSON/nested-small-4                                            358.0 ± ∞ ¹
TreeNodeMarshalJSON/nested-medium-4                                          1.087k ± ∞ ¹
WrapperInjection/full-html-4                                                  37.00 ± ∞ ¹
WrapperInjection/fragment-4                                                   28.00 ± ∞ ¹
ExtractWrapperContent-4                                                       28.00 ± ∞ ¹
ContextOperations/with-statics-4                                              106.0 ± ∞ ¹
ContextOperations/without-statics-4                                           106.0 ± ∞ ¹
TreeNodeClone/flat-4                                                          14.00 ± ∞ ¹
TreeNodeClone/nested-small-4                                                  106.0 ± ∞ ¹
TreeNodeClone/nested-medium-4                                                 322.0 ± ∞ ¹
TreeNodeToMap/flat-4                                                          18.00 ± ∞ ¹
TreeNodeToMap/nested-small-4                                                  120.0 ± ∞ ¹
TreeNodeToMap/nested-medium-4                                                 363.0 ± ∞ ¹
GenerateRandomID-4                                                            1.000 ± ∞ ¹
ExecuteTemplateWithContext_Struct-4                                           29.00 ± ∞ ¹
ExecuteTemplateWithContext_Map-4                                              36.00 ± ∞ ¹
CompareTreesNoChanges-4                                                       2.000 ± ∞ ¹
CompareTreesSmallChange-4                                                     3.000 ± ∞ ¹
CompareTreesLargeChange/10-4                                                  6.000 ± ∞ ¹
CompareTreesLargeChange/100-4                                                 12.00 ± ∞ ¹
CompareTreesLargeChange/1000-4                                                23.00 ± ∞ ¹
RangeDiffUpdate-4                                                             36.00 ± ∞ ¹
RangeDiffInsert-4                                                             36.00 ± ∞ ¹
RangeDiffRemove-4                                                             36.00 ± ∞ ¹
PrepareTreeForClient/with-statics-4                                           0.000 ± ∞ ¹
PrepareTreeForClient/without-statics-4                                        5.000 ± ∞ ¹
Parse/simple-4                                                                43.00 ± ∞ ¹
Parse/conditional-4                                                           64.00 ± ∞ ¹
Parse/range-4                                                                 59.00 ± ∞ ¹
Parse/nested-4                                                                69.00 ± ∞ ¹
Parse/complex-4                                                               126.0 ± ∞ ¹
BuildTree/simple-4                                                            101.0 ± ∞ ¹
BuildTree/conditional-true-4                                                  197.0 ± ∞ ¹
BuildTree/conditional-false-4                                                 103.0 ± ∞ ¹
BuildTree/range-small-4                                                       358.0 ± ∞ ¹
BuildTreeScale/small-10-4                                                    1.128k ± ∞ ¹
BuildTreeScale/medium-100-4                                                  11.03k ± ∞ ¹
BuildTreeScale/large-1000-4                                                  110.1k ± ∞ ¹
NodeRender-4                                                                  3.000 ± ∞ ¹
TreeToHTML/simple-4                                                           4.000 ± ∞ ¹
TreeToHTML/nested-4                                                           7.000 ± ∞ ¹
TreeToHTML/with-ranges-4                                                      5.000 ± ∞ ¹
TreeToHTMLScale/small-10-4                                                    16.00 ± ∞ ¹
TreeToHTMLScale/medium-100-4                                                  109.0 ± ∞ ¹
TreeToHTMLScale/large-1000-4                                                 1.017k ± ∞ ¹
IsVoidElement-4                                                               0.000 ± ∞ ¹
NodeRenderComplex-4                                                           5.000 ± ∞ ¹
MinifyHTML-4                                                                  13.00 ± ∞ ¹
ParseActionFromHTTP-4                                                         30.00 ± ∞ ¹
ParseActionFromWebSocket-4                                                    13.00 ± ∞ ¹
PrepareUpdate/without-errors-4                                                0.000 ± ∞ ¹
PrepareUpdate/with-errors-4                                                   1.000 ± ∞ ¹
SerializeUpdate-4                                                             10.00 ± ∞ ¹
PrepareAndSerialize/simple-update-4                                           11.00 ± ∞ ¹
PrepareAndSerialize/with-metadata-4                                           15.00 ± ∞ ¹
ParseActionScale/small-http-4                                                 30.00 ± ∞ ¹
ParseActionScale/small-ws-4                                                   13.00 ± ∞ ¹
ParseActionScale/medium-http-4                                                40.00 ± ∞ ¹
ParseActionScale/medium-ws-4                                                  23.00 ± ∞ ¹
ParseActionScale/large-http-4                                                 63.00 ± ∞ ¹
ParseActionScale/large-ws-4                                                   46.00 ± ∞ ¹
SerializeUpdateScale/simple-4                                                 10.00 ± ∞ ¹
SerializeUpdateScale/nested-4                                                 19.00 ± ∞ ¹
SerializeUpdateScale/multiple-fields-4                                        16.00 ± ∞ ¹
AsyncSendThroughput-4                                                         1.000 ± ∞ ¹
ConcurrentConnections/000_connections-4                                       10.00 ± ∞ ¹
ConcurrentConnections/100_connections-4                                       100.0 ± ∞ ¹
ConcurrentConnections/:00_connections-4                                      2.000k ± ∞ ¹
RegisterUnregister-4                                                          13.00 ± ∞ ¹
ConcurrentSend-4                                                              1.000 ± ∞ ¹
GetByGroup-4                                                                  1.000 ± ∞ ¹
CloseConnection-4                                                             3.000 ± ∞ ¹
MemoryUsage-4                                                                 618.0 ± ∞ ¹
BroadcastToGroup-4                                                            101.0 ± ∞ ¹
BufferSizes/0_size-4                                                          1.000 ± ∞ ¹
BufferSizes/0_size#01-4                                                       1.000 ± ∞ ¹
BufferSizes/1_size-4                                                          1.000 ± ∞ ¹
BufferSizes/5_size-4                                                          1.000 ± ∞ ¹
BufferSizes/:_size-4                                                          1.000 ± ∞ ¹
ConcurrentRegistrations-4                                                     12.00 ± ∞ ¹
GetByGroupExcept-4                                                            1.000 ± ∞ ¹
geomean                                                                  ²                 ? ³ ² ⁴
¹ need >= 6 samples for confidence interval at level 0.95
² summaries must be >0 to compute geomean
³ benchmark set differs from baseline; geomeans may not be comparable
⁴ ratios must be >0 to compute geomean

Thresholds

  • ⚠️ Warning: Regressions >10% on critical benchmarks
  • ❌ Failure: Regressions >20% on critical benchmarks
  • Critical benchmarks: Benchmark(E2E|Template).*

See benchmarking guide for details.

- Increase hash prefix from 8 to 16 chars for better collision resistance
- Remove TreeNode.Statics in heterogeneous case to avoid ambiguity
- Update MarshalJSON to skip Range.Statics when StaticsMap is present
- Improve Statics field documentation to clarify precedence

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude Bot commented Dec 21, 2025

Code Review

Summary

This PR introduces a solid solution for handling heterogeneous range items (items with different conditional branches producing different statics). The implementation is well-thought-out with good separation of concerns between homogeneous and heterogeneous cases.

Strengths

1. Clean Architecture

  • The rangeItemWithStatics struct provides clear encapsulation for collecting items with their statics
  • The buildRangeTreeWithStatics function cleanly separates homogeneous vs heterogeneous logic
  • Hash-based deduplication is efficient and avoids duplicate statics in the wire format

2. Hash Collision Resistance

  • Using 16 hex characters (64 bits) from SHA-256 provides strong collision resistance
  • For context: with 1,000,000 unique statics variants, collision probability is ~10^-10 (essentially zero)
  • The separator byte (\x00) between strings prevents hash collisions from different string concatenations

3. Wire Format Optimization

  • Homogeneous ranges continue using the original efficient format (backward compatible)
  • Heterogeneous ranges use sm (statics map) only when necessary
  • Per the CLAUDE.md spec, statics are properly excluded from updates when clients have them cached

4. Documentation

  • Clear comments explaining the precedence between Statics and StaticsMap
  • Good wire format examples in PR description

Issues & Concerns

1. Missing Client-Side Implementation 🔴 BLOCKING
The PR description mentions:

Related PRs: livetemplate/client: Same branch name with client-side rendering changes

However, this PR modifies the wire format with new sm and _sk keys. The client MUST be updated to handle these before this can be merged, otherwise:

  • Clients will fail to render heterogeneous ranges
  • Items will render without proper statics (missing HTML structure)
  • Browser errors will occur

Action: Verify the client PR exists and is ready to merge atomically with this change.

2. Missing Test Coverage 🔴 BLOCKING
The only test change is updating an expected value in tree_test.go:382. Critical missing tests:

// Needed tests:
// 1. Heterogeneous range with multiple different statics
func TestHeterogeneousRangeStatics(t *testing.T) {
    // Range with {{if}} producing different input types
    // Verify: StaticsMap populated, _sk keys correct, Statics is nil
}

// 2. JSON marshaling/unmarshaling round-trip
func TestStaticsMapSerialization(t *testing.T) {
    // Marshal tree with StaticsMap, unmarshal, verify equality
}

// 3. Homogeneous range still uses old format
func TestHomogeneousRangeBackwardCompat(t *testing.T) {
    // Range with same statics for all items
    // Verify: No StaticsMap, Statics field used, no _sk keys
}

// 4. Edge case: Empty range
func TestEmptyHeterogeneousRange(t *testing.T) {
    // Verify: No panic, proper empty range structure
}

// 5. Clone() with StaticsMap
func TestTreeNodeCloneWithStaticsMap(t *testing.T) {
    // Verify: Deep copy of StaticsMap works correctly
}

The PR description mentions TestExecArgsForm but this test doesn't appear to exist in the codebase. Where is this test?

3. Potential Memory Overhead
For heterogeneous ranges, each item now includes a _sk field (string). Considerations:

  • Homogeneous case: No overhead (good!)
  • Heterogeneous case: 16 bytes per item + map overhead
  • Concern: For large lists with many variants, this could add up

Mitigation suggestions:

  • Document in CLAUDE.md when this pattern should be avoided (e.g., "avoid deeply nested conditionals in range templates")
  • Consider adding a metric/log warning if StaticsMap size exceeds a threshold (e.g., >100 unique variants)

4. Ambiguous State in RangeData
The documentation states:

When StaticsMap is present (heterogeneous ranges), consumers should use StaticsMap... In this case, Statics should be nil.

However, the code in types.go:347 allows both to be present:

if len(tn.Range.Statics) > 0 && len(tn.Range.StaticsMap) == 0 {
    result["s"] = tn.Range.Statics
}

Issue: What if a bug causes both Statics and StaticsMap to be populated? The marshaling code skips Statics silently, but this could mask bugs.

Suggestion: Add validation in buildRangeTreeWithStatics:

rangeTree.Range = &RangeData{
    Items:      itemTrees,
    Statics:    nil, // Not used when StaticsMap is present
    StaticsMap: staticsMap,
}
// Add assertion in dev mode:
if len(rangeTree.Range.Statics) > 0 && len(rangeTree.Range.StaticsMap) > 0 {
    panic("BUG: Both Statics and StaticsMap are populated")
}

5. Hash Function Not Cryptographically Necessary
Using SHA-256 for deduplication is overkill and slower than necessary. Since this is not for security, consider:

  • Option 1: Use a faster non-crypto hash (e.g., xxhash, fnv)
  • Option 2: Use Go's built-in hash/fnv (in stdlib, no dependencies)
import "hash/fnv"

func hashStatics(statics []string) string {
    h := fnv.New64a() // Much faster than SHA-256
    for _, s := range statics {
        h.Write([]byte(s))
        h.Write([]byte{0})
    }
    return fmt.Sprintf("%016x", h.Sum64())[:16]
}

Benchmark expectation: ~10x faster for typical statics arrays.

6. Missing CLAUDE.md Updates
The CLAUDE.md file should be updated to document:

  • The new heterogeneous range feature
  • When StaticsMap is used vs Statics
  • Wire format examples for heterogeneous ranges
  • Performance considerations (memory overhead per item)

Minor Issues

7. Typo in Comment (internal/parse/range.go:27)

// rangeItemWithStatics holds an item tree and its statics together.

Consider adding more context:

// rangeItemWithStatics holds an item tree with its statics and hash.
// Used during range processing to detect heterogeneous statics patterns.

8. Deprecated Function Should Be Removed
buildRangeTree is marked deprecated but still exists. Consider:

  • Remove it if nothing uses it
  • Or keep it but add a runtime deprecation warning

9. Clone() Missing Initialization Check
In types.go:557, the clone initializes StaticsMap but doesn't check if tn.Range.StaticsMap is nil:

if len(tn.Range.StaticsMap) > 0 {  // This is correct, but be explicit:
    clone.Range.StaticsMap = make(map[string][]string, len(tn.Range.StaticsMap))

This is fine, but could add a comment: // StaticsMap is nil-safe due to len() check

Performance Considerations

Memory Usage:

  • Before: ~100 bytes per range (single statics array)
  • After (heterogeneous): ~100 bytes + (16 bytes × num_items) + (statics_size × num_unique_variants)
  • Impact: Acceptable for typical use cases (<100 items, <10 variants)

CPU Usage:

  • Hash computation: O(n × m) where n=num_items, m=avg_statics_length
  • Homogeneity check: O(n) comparison
  • Impact: Negligible for typical ranges

Security Concerns

No security issues identified. The hash is used only for deduplication, not authentication. Collision resistance is sufficient with 64-bit hash space.

Recommendations

Before Merging:

  1. ✅ Add comprehensive test coverage (see section wip: session design #2)
  2. ✅ Verify client-side PR is ready
  3. ✅ Update CLAUDE.md documentation
  4. ⚠️ Consider switching to FNV hash (performance)
  5. ⚠️ Add validation to prevent Statics+StaticsMap ambiguity

After Merging:

  • Monitor memory usage in production with many heterogeneous ranges
  • Consider adding observability metrics for StaticsMap usage

Conclusion

This is a well-designed solution to a real problem. The code quality is high, and the approach is sound. However, it should not be merged until:

  1. Client-side implementation is confirmed ready
  2. Test coverage is added
  3. Documentation is updated

The implementation correctly handles the wire format optimization per the tree-update-specification, properly excluding statics from updates when clients have them cached.

Overall Assessment: 👍 Approve pending completion of blocking items above.


Reviewed with reference to CLAUDE.md guidelines: 5-phase architecture, wire format optimization, testing strategy, and development conventions.

For empty→items transitions, the diff engine needs access to item
statics to build proper differential operations. Previously, when
ShouldIncludeStatics() returned false (client has cached statics),
both newNode.Statics and Range.Statics were nil, causing the client
to receive null statics.

This fix:
1. Always sets Range.Statics in buildRangeTreeWithStatics for internal
   use, independent of TreeNode.Statics (wire format optimization)
2. Adds fallback logic in extractRangeData to check Range.Statics
   when TreeNode.Statics is nil

This ensures the todos example and similar empty→items patterns work
correctly when items are added to an initially empty range.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude Bot commented Dec 22, 2025

Code Review: Heterogeneous Range Statics Support

Great work on this PR! The implementation solves a real problem with range items that have different conditional branches.

✅ Strengths

1. Solid Architecture

  • Clean separation between homogeneous and heterogeneous cases
  • Hash-based deduplication is efficient - only stores unique statics variants
  • 16-char hash prefix (64 bits) provides excellent collision resistance
  • Backward compatible wire format

2. Well-Documented Changes

  • Clear comments explaining StaticsMap usage
  • Good deprecation notice for buildRangeTree()
  • PR description provides excellent context

3. Code Quality

  • Proper error handling with descriptive messages
  • Clean helper functions
  • Correct deep cloning of StaticsMap

🔍 Critical Issues

1. Missing Test Coverage (BLOCKER)
The PR mentions TestExecArgsForm but this test does not exist. I verified by running go test which showed no tests to run.

Required tests:

  • Heterogeneous range with 2+ different statics variants
  • JSON marshal/unmarshal round-trip for StaticsMap
  • Clone() preserves StaticsMap correctly
  • Empty to items transition with heterogeneous items
  • Hash collision handling

Add tests in internal/parse/range_test.go and internal/build/types_test.go before merging.

2. Performance Impact
The code hashes statics for EVERY range item even when homogeneous (range.go:143-148). For 1000-item homogeneous ranges this means 1000 SHA256 hashes computed and discarded.

Consider early-exit optimization or defer hashing until AFTER detecting heterogeneity.

3. Missing Client PR Link
PR mentions related client changes but no link provided. This creates deployment risk if server deploys before client understands the sm field.

Link to client PR and verify end-to-end compatibility before merging.

🐛 Other Issues

4. Statics Field Ambiguity
Comment says Statics should be nil but code enforces it. Update comment to MUST be nil for clarity.

5. Test Change Unexplained
tree_test.go adds statics to expected output but PR does not explain why. Was the test wrong before?

6. Complex Fallback Logic
range_ops.go:78-87 has nested fallbacks that may indicate upstream issue. Add assertions or tests to validate invariant.

📊 Performance & Security

Performance:

  • Memory: O(variants) space for heterogeneous, no change for homogeneous
  • CPU: SHA256 hashing cost justified for heterogeneous but wasted for homogeneous

Security:
No concerns. Hash truncation safe, proper JSON validation.

Action Items

  1. [BLOCKER] Add comprehensive tests for heterogeneous ranges
  2. [HIGH] Link to client PR and verify compatibility
  3. [MEDIUM] Optimize hashing for homogeneous case
  4. [MEDIUM] Clarify Statics nil invariant
  5. [LOW] Explain tree_test.go change
  6. [LOW] Add benchmarks

Recommendation: Address items 1 and 2 before merging. Others can be follow-ups.

Great work overall! Solution is architecturally sound and will fix the issues. Just needs test validation.

@adnaan adnaan merged commit b0d3545 into main Dec 22, 2025
4 of 5 checks passed
@adnaan adnaan deleted the fix/heterogeneous-range-statics branch December 22, 2025 06:54
adnaan added a commit that referenced this pull request Apr 25, 2026
Ticks all Session 4 boxes (patterns #17-21 — Modal Dialog, Confirm Dialog,
Tabs HATEOAS, SPA Navigation, Keyboard Shortcuts) and links the merged PR
livetemplate/examples#74 plus the four follow-up issues filed from the
review (#75-#78).

Appends six new Implementation Notes from Session 4's lessons:

- Forms inside <dialog> need BindAndValidate, not ctx.ValidateForm
  (schema-walk doesn't reliably cover dialog-nested forms in v0.8.21)
- Button name shadows form name in action resolution; two safe shapes
- t.Cleanup-based restore for fetch-mock subtests (avoids cascade leak
  if chromedp step fails mid-subtest)
- Conditional event-listener placement to avoid no-op WS round-trips on
  state-dependent global keydown bindings
- Local-Docker chromedp small-WS-frame delivery delay (5-6s tail; CI
  unaffected; investigate Docker bridge, not the framework)
- AI review loop convergence — "real bugs by round 4" heuristic with
  the Session 4 PR's 11-round trajectory as data

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants