Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
4d70d90
wip - store revisions
kennyfrc Jun 21, 2024
cd59845
wip - more revisions
kennyfrc Jun 21, 2024
f8ce896
wip - invariant update
kennyfrc Jun 21, 2024
a802c2b
fix - query key
kennyfrc Jun 21, 2024
0bfe9d8
wip - fixes query key bug
kennyfrc Jun 21, 2024
c4dd4b9
wip - new slice mechanism - some tests fixed
kennyfrc Jun 22, 2024
7f58288
wip - store state
kennyfrc Jun 22, 2024
eaf277c
wip - slice improvements
kennyfrc Jun 23, 2024
49778b9
wip - call it model
kennyfrc Jun 24, 2024
6e8ec45
wip - fix fsm
kennyfrc Jul 1, 2024
baa7582
wip - comments
kennyfrc Jul 1, 2024
5d92a88
wip - better validation
kennyfrc Jul 1, 2024
23a0ace
wip - improve api
kennyfrc Jul 2, 2024
cbb6ed1
wip - update api
kennyfrc Jul 2, 2024
3e975c9
wip - add trigger, memo to store
kennyfrc Jul 14, 2024
102072f
wip - keep it simple - just a store instead of adding slices or models
kennyfrc Jul 15, 2024
292f8af
wip - async actions or thunks
kennyfrc Jul 15, 2024
09a9ec0
wip - improve our thunk
kennyfrc Jul 15, 2024
c98204e
wip - better thunk example (sequential dependencies)
kennyfrc Jul 16, 2024
505966c
wip - defineThunk and dispatchAsync
kennyfrc Jul 16, 2024
9db9f23
wip - cleaner defineMachine, add dispatchAsync to all contexts
kennyfrc Jul 16, 2024
a369838
wip - add examples for thunks
kennyfrc Jul 16, 2024
1e3feb0
wip - make query and mutate thenable
kennyfrc Jul 16, 2024
45ae85a
wip - dispatch tweaks
kennyfrc Jul 16, 2024
8c89e60
wip - cyclic dependency detection
kennyfrc Jul 16, 2024
ac8c231
wip - rename `thunk` to `asyncAction` to make it more newbie friendly…
kennyfrc Jul 17, 2024
9767c80
wip - rollback returning values in actions (not allowed w/ immer)
kennyfrc Jul 17, 2024
a9267ce
wip - better error messages
kennyfrc Jul 22, 2024
af12594
wip - catch undefined actions in dispatch (common in handrolled state…
kennyfrc Jul 23, 2024
101f6e0
wip - hooks and schema
kennyfrc Jul 27, 2024
211610f
wip - types for schema
kennyfrc Jul 28, 2024
30f1e36
wip - gitignore
kennyfrc Jul 28, 2024
66fa849
revert - remove wip code
kennyfrc Jul 28, 2024
554defb
wip - action specs
kennyfrc Jul 28, 2024
4da0b53
wip - tweak postcondition
kennyfrc Jul 28, 2024
de26255
wip - tweak dependent types
kennyfrc Jul 29, 2024
dea54c0
wip - fix dependent sum type
kennyfrc Jul 29, 2024
15588e0
wip - feat(model) - added model, fixed tests
kennyfrc Aug 1, 2024
0eefda8
wip - feat(model) - revise model implementation
kennyfrc Aug 10, 2024
f89a2be
wip - feat(idb) - first cut of idb
kennyfrc Aug 11, 2024
dea729f
wip - refactor(idb), feat(localStorage) - improve code readability of…
kennyfrc Aug 12, 2024
cec544a
wip - tests - add tests for idb and localstorage
kennyfrc Aug 12, 2024
91f5c17
wip - fix(tests) - localStorage
kennyfrc Aug 12, 2024
54974fe
wip - refactor(localStorage) - improve ls api
kennyfrc Aug 12, 2024
a456ef3
wip - fix(hooks) - type revisions work in prod but tests need to be u…
kennyfrc Aug 12, 2024
5371172
wip - style - just use default prettier moving forward
kennyfrc Aug 13, 2024
98577b6
wip - refactor(tests) - use our own test suite instead of jasmine
kennyfrc Aug 14, 2024
cf74a8c
wip - style - prettier
kennyfrc Aug 14, 2024
ce7f2df
wip - fix(types) - allow partial validation
kennyfrc Aug 14, 2024
47156da
wip - refactor(tests) - just change state to getState()
kennyfrc Aug 14, 2024
c93e61b
wip - fix(state-machine) - address various state machine bugs and add…
kennyfrc Aug 14, 2024
90ef7bc
wip - feat(url-store) - our client-side router alternative: reactive …
kennyfrc Aug 14, 2024
bab63cc
wip - refactor(url-store) - same interface as observable store. can n…
kennyfrc Aug 20, 2024
f2699d4
wip - refactor(url-store) - improve title defaults
kennyfrc Aug 25, 2024
79b888c
wip - feat(reactive-element) - new interaction context in elementf
kennyfrc Sep 2, 2024
c3e72d7
wip - chore - doc updates
kennyfrc Sep 2, 2024
4469a02
feat: Add example demonstrating dispatch mechanisms across multiple s…
kennyfrc Dec 1, 2024
12a23fc
feat: Add detailed logging to dispatch test for synchronous operation…
kennyfrc Dec 1, 2024
e4dafca
docs: Add cami.md documentation file
kennyfrc Dec 1, 2024
2a75fcb
I'll generate a concise commit message for this documentation update:
kennyfrc Dec 1, 2024
a117e1c
feat: Add async dispatch and cross-store state mutation example
kennyfrc Dec 1, 2024
0b741db
feat: Implement deep cloning and proxy handling for ObservableProxy
kennyfrc Jan 23, 2025
600fc6d
fix: Resolve maximum call stack size exceeded error in ObservableProxy
kennyfrc Jan 23, 2025
6a97655
copy from prod
kennyfrc Jun 19, 2025
aba5f4c
feat: Migrate tests to Vitest and add jsdom environment
kennyfrc Jun 19, 2025
acc655f
feat: Refactor and improve test suite reliability
kennyfrc Jun 20, 2025
9c7e89a
test: Improve hook and store test stability
kennyfrc Jun 20, 2025
80c6b25
fix: Pass correct previousState to machine onEntry
kennyfrc Jun 20, 2025
deaa841
docs: Add comprehensive testing guidelines
kennyfrc Jun 20, 2025
9ffcee2
chore: Remove testing guidelines file
kennyfrc Jun 20, 2025
0468c73
Merge branch 'session/vitest' into v0.4.0
kennyfrc Jun 20, 2025
8121c17
fix: merge issues
kennyfrc Jun 20, 2025
f9eb61e
refactor: Rename internal action and payload variables
kennyfrc Jun 20, 2025
0494df9
build
kennyfrc Jun 20, 2025
31541a9
[claudesquad] update from 'typescript' on 22 Jun 25 22:32 PST (paused)
kennyfrc Jun 22, 2025
1c01239
(no commit message provided)
kennyfrc Jun 22, 2025
6e2f70a
refactor(build): updated ts port
kennyfrc Jul 8, 2025
36716e2
chore: comments
kennyfrc Jul 9, 2025
e1d5ce3
chore: reorganize
kennyfrc Jul 9, 2025
673dad9
[claudesquad] update from 'benchmarks' on 09 Jul 25 13:29 PST (paused)
kennyfrc Jul 9, 2025
b1ae26a
perf: _deepEqual improvements
kennyfrc Jul 9, 2025
ba09113
chore: clean
kennyfrc Jul 9, 2025
0f2687e
chore: type check
kennyfrc Jul 9, 2025
d04fb33
wip: copy from prod
kennyfrc Jul 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ out
/examples/build

package-lib.json
src/scratch.js
src/scratch*.js
src/spike.js
src/test.js
src/test*.js
src/assert.js
src/runner.js
.aider*
60 changes: 29 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

⚠️ Expect API changes until v1.0.0 ⚠️

Current version: 0.3.23.
Current version: 0.4.0

Bundle Size: 14kb minified & gzipped.
Bundle Size: 15kb minified & gzipped.

A simple yet powerful toolkit for interactive islands in web applications. No build step required.

Expand All @@ -20,7 +20,7 @@ Note that Cami specializes in bringing rich interactivity to your web applicatio
const { html, ReactiveElement } = cami;

class CounterElement extends ReactiveElement {
count = 0
count = 0;

template() {
return html`
Expand All @@ -31,21 +31,21 @@ Note that Cami specializes in bringing rich interactivity to your web applicatio
}
}

customElements.define('cami-counter', CounterElement);
customElements.define("cami-counter", CounterElement);
</script>
```

[Documentation](https://camijs.com/) | [API Reference](https://camijs.com/api/) | [CDN Link](https://unpkg.com/cami@latest/build/cami.cdn.js) | [Introduction](https://camijs.com/)

## Learn By Example

* [Counter](https://camijs.com/learn_by_example/counter/)
* [Interactive Registration Form](https://camijs.com/learn_by_example/form_validation/)
* [Todo List with Server State Management](https://camijs.com/learn_by_example/todo_list_server/)
* [Cart with Client & Server State Management](https://camijs.com/learn_by_example/cart/)
* [Blog with Optimistic UI](https://camijs.com/learn_by_example/blog/)
* [Nested Key Updates](https://camijs.com/learn_by_example/nested_updates/)
* [WAI-ARIA Compliant Modal](https://camijs.com/learn_by_example/modal/)
- [Counter](https://camijs.com/learn_by_example/counter/)
- [Interactive Registration Form](https://camijs.com/learn_by_example/form_validation/)
- [Todo List with Server State Management](https://camijs.com/learn_by_example/todo_list_server/)
- [Cart with Client & Server State Management](https://camijs.com/learn_by_example/cart/)
- [Blog with Optimistic UI](https://camijs.com/learn_by_example/blog/)
- [Nested Key Updates](https://camijs.com/learn_by_example/nested_updates/)
- [WAI-ARIA Compliant Modal](https://camijs.com/learn_by_example/modal/)

## Key Concepts:

Expand All @@ -65,34 +65,34 @@ To use it, you'll need to create a custom element and register it with `customEl
<script src="https://unpkg.com/cami@latest/build/cami.cdn.js"></script>
<article>
<h1>Counter</h1>
<counter-component
></counter-component>
<counter-component></counter-component>
</article>
<script type="module">
const { html, ReactiveElement } = cami;

class CounterElement extends ReactiveElement {
count = 0

template() {
return html`
<button @click=${() => this.count--}>-</button>
<button @click=${() => this.count++}>+</button>
<div>Count: ${this.count}</div>
`;
customElements.define(
"counter-component",
class extends ReactiveElement {
count = 0;

template() {
return html`
<button @click=${() => this.count--}>-</button>
<button @click=${() => this.count++}>+</button>
<div>Count: ${this.count}</div>
`;
}
}
}

customElements.define('counter-component', CounterElement);
);
</script>
```

## Features include:

* **Reactive Web Components**: Simplifies front-end web development with `ReactiveElement`. This is done through [Observable Properties](https://camijs.com/features/observable_property). They are properties of a `ReactiveElement` instance that are automatically observed for changes. When a change occurs, the `ReactiveElement` instance is notified and can react accordingly by re-rendering the component. Observable properties support deep updates, array changes, and reactive attributes, making it easier to manage dynamic content. Lastly, this removes the boilerplate of `signal()`, `setState()`, or `reactive()` that you might find in other libraries.
* **Async State Management**: Easily manage server data. Our library provides a simple API for fetching and updating data with [`query` and `mutation`](https://camijs.com/features/async_state_management). Use the `query` method to fetch and cache data, with options to control how often it refreshes. The `mutation` method lets you update data and immediately reflect those changes in the UI, providing a smooth experience without waiting for server responses.
* **Cross-component State Management with Stores**: Share state across different components with ease using a single store using [`cami.store`](https://camijs.com/features/client_state_management). By default, this uses `localStorage` to persist state across page refreshes. This is useful for storing user preferences, authentication tokens, and other data that needs to be shared across components. This is also useful for storing data that needs to be shared across tabs.
* **Streams & Functional Reactive Programming (FRP)**: Handle asynchronous events gracefully with [Observable Streams](https://camijs.com/features/streams/). They offer powerful functions like `map`, `filter`, `flatMap`, and `debounce` to process events in a sophisticated yet manageable way, for clean & declarative code.
- **Reactive Web Components**: Simplifies front-end web development with `ReactiveElement`. This is done through [Observable Properties](https://camijs.com/features/observable_property). They are properties of a `ReactiveElement` instance that are automatically observed for changes. When a change occurs, the `ReactiveElement` instance is notified and can react accordingly by re-rendering the component. Observable properties support deep updates, array changes, and reactive attributes, making it easier to manage dynamic content. Lastly, this removes the boilerplate of `signal()`, `setState()`, or `reactive()` that you might find in other libraries.
- **Async State Management**: Easily manage server data. Our library provides a simple API for fetching and updating data with [`query` and `mutation`](https://camijs.com/features/async_state_management). Use the `query` method to fetch and cache data, with options to control how often it refreshes. The `mutation` method lets you update data and immediately reflect those changes in the UI, providing a smooth experience without waiting for server responses.
- **Cross-component State Management with Stores**: Share state across different components with ease using a single store using [`cami.store`](https://camijs.com/features/client_state_management). By default, this uses `localStorage` to persist state across page refreshes. This is useful for storing user preferences, authentication tokens, and other data that needs to be shared across components. This is also useful for storing data that needs to be shared across tabs.
- **Streams & Functional Reactive Programming (FRP)**: Handle asynchronous events gracefully with [Observable Streams](https://camijs.com/features/streams/). They offer powerful functions like `map`, `filter`, `flatMap`, and `debounce` to process events in a sophisticated yet manageable way, for clean & declarative code.

Please visit our [Documentation](https://camijs.com/), [API Reference](https://camijs.com/api/), [Examples](https://camijs.com/learn_by_example/counter/), or [Core Concepts](https://camijs.com/features/observable_property/) to learn more.

Expand All @@ -109,7 +109,6 @@ That said, I like the idea of declarative templates, uni-directional data flow,
- **Lean Teams or Solo Devs**: If you're building a small to medium-sized application, I built Cami with that in mind. You can start with `ReactiveElement`, and once you need to share state between components, you can add our store. It's a great choice for rich data tables, dashboards, calculators, and other interactive islands. If you're working with large applications with large teams, you may want to consider other frameworks.
- **Developers of Multi-Page Applications**: For folks who have an existing server-rendered application, you can use Cami to add interactivity to your application.


## Examples

To learn Cami by example, see our [examples](https://camijs.com/learn_by_example/counter/).
Expand All @@ -135,7 +134,6 @@ JSDoc is used to build the API reference. We use Material for MkDocs for the doc

To make JSDoc be compatible with MkDocs, we use jsdoc2md to generate markdown files from JSDoc comments. We use then use MkDocs to build the documentation site.


### Testing

We use Jasmine for testing. To run the tests, run:
Expand Down
226 changes: 226 additions & 0 deletions analysis/bugs/20250709-1329_deepEqual_correctness_analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
# deepEqual Correctness Analysis

**Date:** 2025-07-09 13:29
**File:** src/utils.ts
**Function:** _deepEqual
**Status:** Currently at 98.2% correctness (56/57 test cases passing)
**Goal:** Achieve 100% correctness without performance degradation

## Overview

Analysis of the `_deepEqual` implementation in comparison to Lodash's `isEqual` to identify and fix the remaining correctness issues while maintaining optimal performance.

## Current State

### Implementation Summary
- **Data Shape**: Function takes `(a: any, b: any, visited?: Set<any>): boolean`
- **Architecture**: Stack-based circular reference detection using `Set`
- **Performance**: 92.4% of fast-deep-equal performance on simple objects
- **Correctness**: 98.2% (56/57 test cases passing)

### Data Flow Analysis

1. **Entry Point**: `_deepEqual(a, b, visited?)`
2. **Early Exits**:
- `a === b` (reference equality)
- `a !== a` (NaN handling)
- `null/undefined` checks
- Type mismatches for primitives
3. **Circular Reference Detection**: Initialize/check `visited` Set
4. **Type-Specific Comparisons**: Arrays, Dates, RegExp, Maps, Sets, TypedArrays
5. **Object Comparison**: Constructor check, key enumeration, recursive comparison

## Critical Issues Identified

### 1. Circular Reference Handling (Primary Bug)

**Current Implementation:**
```typescript
// Check for circular references
if (visited.has(a) || visited.has(b)) {
return true; // Assume equal for circular structures
}
```

**Problem**: This assumes ALL circular references are equal, which is incorrect.

**Lodash's Approach** (from provided code):
```javascript
// Check that cyclic values are equal.
var objStacked = stack.get(object);
var othStacked = stack.get(other);
if (objStacked && othStacked) {
return objStacked == other && othStacked == object;
}
```

**Impact**: Causes false positives when comparing different circular structures.

### 2. Map Key Comparison Issue

**Current Implementation:**
```typescript
for (const [key, val] of a.entries()) {
if (!b.has(key) || !_deepEqual(val, b.get(key), visited)) {
// ...
}
}
```

**Problem**: Map keys are compared with strict equality (`has(key)`) but should use deep equality for object keys.

**Lodash's Approach**: Uses deep comparison for both keys and values in Map structures.

### 3. Set Comparison Inefficiency

**Current Implementation**: O(n²) comparison with redundant `matched` array tracking.

**Lodash's Approach**: More efficient comparison strategies for Sets.

### 4. Constructor Comparison Edge Cases

**Current Implementation:**
```typescript
if (a.constructor !== b.constructor) {
return false;
}
```

**Problem**: Doesn't handle cases where constructor might be undefined or cases with inheritance.

**Lodash's Approach**: More robust constructor handling with additional checks.

## Performance-Neutral Fixes

### 1. Fix Circular Reference Detection

**Current (Incorrect):**
```typescript
if (visited.has(a) || visited.has(b)) {
return true; // Assume equal for circular structures
}
```

**Fix (Performance-Neutral):**
```typescript
if (visited.has(a) || visited.has(b)) {
// For circular structures, check if they're the same circular reference
return visited.has(a) && visited.has(b) &&
a === b; // Same reference means same circular structure
}
```

### 2. Improve Map Key Comparison

**Current:**
```typescript
if (!b.has(key) || !_deepEqual(val, b.get(key), visited)) {
```

**Fix (Slight Performance Cost but Correct):**
```typescript
let found = false;
for (const [bKey, bVal] of b.entries()) {
if (_deepEqual(key, bKey, visited)) {
if (!_deepEqual(val, bVal, visited)) {
return false;
}
found = true;
break;
}
}
if (!found) return false;
```

### 3. Optimize Set Comparison

**Current O(n²) approach can be improved while maintaining correctness:**
```typescript
// More efficient Set comparison
if (a instanceof Set) {
if (!(b instanceof Set) || a.size !== b.size) {
return false;
}

// Early exit for empty sets
if (a.size === 0) return true;

// For primitive sets, use Set operations
if (isPrimitiveSet(a) && isPrimitiveSet(b)) {
return [...a].every(val => b.has(val));
}

// For complex sets, use the current approach but optimize
// ... existing nested loop logic
}
```

## Lodash Comparison Analysis

### Key Differences from Lodash

1. **Stack vs Set**: Lodash uses a Stack data structure for circular reference detection
2. **Bitmask Flags**: Lodash uses bitmask flags for partial comparison support
3. **Wrapper Object Handling**: Lodash handles `__wrapped__` objects
4. **Constructor Validation**: More robust constructor checking

### Performance Tradeoffs

- **Lodash**: 100% correctness, 25.6% performance vs fast-deep-equal
- **Current**: 98.2% correctness, 92.4% performance vs fast-deep-equal
- **Target**: 100% correctness, maintain >90% performance

## Recommendations

### High Priority (Correctness Fixes)

1. **Fix Circular Reference Detection**
- Implement proper circular reference comparison
- Use WeakMap for better performance than Set
- Match Lodash's approach for circular structure validation

2. **Fix Map Key Comparison**
- Implement deep equality for Map keys
- Handle object keys correctly

### Medium Priority (Edge Case Handling)

3. **Improve Constructor Checking**
- Handle undefined constructors
- Add inheritance checks similar to Lodash

4. **Add Wrapper Object Support**
- Handle `__wrapped__` objects if needed for full compatibility

### Low Priority (Performance Optimizations)

5. **Optimize Set Comparison**
- Implement primitive set fast path
- Reduce O(n²) complexity where possible

## Implementation Strategy

1. **Phase 1**: Fix circular reference detection (highest impact)
2. **Phase 2**: Fix Map key comparison
3. **Phase 3**: Optimize Set comparison
4. **Phase 4**: Add remaining edge case handling

## Test Cases to Focus On

Based on the benchmark results, focus on:
- Circular reference scenarios
- Map with object keys
- Complex Set comparisons
- Constructor edge cases

## Expected Outcome

With these fixes, the implementation should achieve:
- **100% correctness** (57/57 test cases passing)
- **>90% performance** relative to fast-deep-equal
- **Maintain compatibility** with existing usage patterns

---

*Analysis completed: 2025-07-09 13:29*
*Next steps: Implement Phase 1 fixes for circular reference detection*
Loading