Skip to content

Live view (wrap-on-set / api.add): object- and function-valued writes through the view don't reach the underlying object, and shadow it #495

Closed

Description

@Shinrai

Summary

Since 3.15.3 (#342, fixing #340), self.X = obj and api.slothlet.api.add() are described as giving "a genuinely live, two-way view onto the underlying object" (v3.15.3 changelog). Primitive writes through the view are two-way. Object and function writes are not:

  • the value is stored on the wrapper only and never reaches the underlying object;
  • from then on the wrapper shadows that key, so later updates to the real object under it are invisible through the view.

Cause

setTrap in src/lib/handlers/unified-wrapper.mjs (around lines 4771–4890 on master/next, unchanged through 3.21.1):

  • it forwards with Reflect.set(liveImpl, prop, value) only when !isObjectOrFunctionValue (~4809/4821);
  • object and function values fall through to wrap-on-set and Object.defineProperty(wrapper, prop, { value: stored, writable: false }) (~4884) on the wrapper itself.

The #340 regression suite (tests/vitests/suites/handlers/unified-wrapper-live-delegation.test.vitest.mjs) covers only primitive writes.

Repro

Behaviour is identical on 3.15.3 and 3.21.0, in both eager and lazy modes.

// api/mod.mjs
import { self } from "@cldmv/slothlet/runtime";
export function assign() { const held = { n: 1, arr: [], nested: {} }; globalThis.__held = held; self.mod.x = held; }
const api = await slothlet({ dir: "./api", mode: "eager" });
api.mod.assign(); const held = globalThis.__held;
api.mod.x.n = 10;              held.n          // 10        ✓ primitive forwards
api.mod.x.arr = ["cmd"];       held.arr        // []        ✗ expected ["cmd"]
api.mod.x.nested.c = { a: 1 }; held.nested.c  // undefined ✗ expected { a: 1 }
held.arr = ["later"];          api.mod.x.arr   // ["cmd"]   ✗ the view now shadows the real key

The same happens through an add()-mounted leaf:

  • self.devices.d1.connection = conn; api.devices.d1.connection.deviceFeatures = ["cmd"] leaves conn.deviceFeatures as [].
  • A leaf method that reads the real conn still sees [].
  • Function-valued writes (e.g. connection.onUnhandledPacket = fn) take the same branch.

With 3.15.1 (raw reference), every line gave the expected result.

Expected

On a live-identity wrapper, an object or function write should do what primitive writes already do: Reflect.set onto the live impl, and serve the value back lazily wrapped from the impl on read. The view then stays two-way and never shadows the impl.

This needs to fit with how userAssigned values are preserved across reload and with ownership tracking.

If the intended contract really is "object writes stay on the view", then the v3.15.3 changelog and docs/CONTEXT-PROPAGATION.md should say so, and #338's original case remains open.

Not in scope

api.mod.x !== obj (and nested !==) is expected. The docs say the value gets a UnifiedWrapper, and instanceof, methods, native this substitution and socket I/O all work through the view.

Refs: #338, #340, #342. Surfaced in CLDMV/droidsock:

  • src/api/device.mjs:177-178 assigns the session onto the mounted leaf;
  • droidsock PR release: v2.3.1 #48 (commit 569531e) had to stop writing device.connection.deviceFeatures in its tests.

Activity

  1. added
    type: bugSomething is broken or not behaving as expected
    priority: highNeeds attention soon — not blocking, but don't let it sit
    area: coreTouches core library / runtime source code
    status: in progressPartially implemented — actively being built
    on Sep 28, 2026
  2. added theissue type on Sep 28, 2026
  3. added
    status: implementedBuilt and deployed, but not yet fully tested/verified
    and removed
    status: in progressPartially implemented — actively being built
    on Sep 29, 2026
  4. Shinrai commented on Sep 29, 2026

    @Shinrai
    ContributorAuthor

    Landed on next in #501 (9d9dca34). Ships in the next release (3.22.0); this closes when it reaches master.

  5. cldmv-bot commented on Oct 3, 2026

    @cldmv-bot
    Contributor

    Closed in v3.22.0 — resolved by #501.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: coreTouches core library / runtime source codepriority: highNeeds attention soon — not blocking, but don't let it sitstatus: implementedBuilt and deployed, but not yet fully tested/verifiedtype: bugSomething is broken or not behaving as expected

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions