@@ -8367,9 +8367,9 @@ export class ObjectStackProtocolImplementation implements
83678367 // [#21334] An upsert by name replaces whatever the merge seated
83688368 // under that name, so the name has to be one the container may
83698369 // write. {@link expandRuntimeViewContainer} decides it: on
8370- // another package's object a bare `list` expands under the
8371- // container's own name, never over that package's
8372- // `<object>.default` .
8370+ // another package's object every name a container expands
8371+ // derives from the container's own name, never one of that
8372+ // package's `<object>.<key>` names .
83738373 if (isView) {
83748374 const byName = new Map<string, unknown>();
83758375 for (const it of items as any[]) {
@@ -16139,30 +16139,40 @@ export class ObjectStackProtocolImplementation implements
1613916139 * kept, unchanged, for every container written before this field was
1614016140 * consulted here.
1614116141 *
16142- * ## A bare `list` on ANOTHER package's object (#21334)
16143- *
16144- * The spec names a bare `list` (one that names no key) `<object>.default`.
16145- * For a container that belongs to the object's own package that is the
16146- * object's default view, and it still expands there. For a container
16147- * saved under any other name, in another package or in none, on an object
16148- * a code package owns, `<object>.default` is the OTHER package's item:
16149- * ADR-0005 keys an overlay by its own name, and ADR-0126 rules out a
16150- * silent override, so a row named `x` may not replace an item named
16151- * `<object>.default`. Both callers set each expansion by name — the list's
16142+ * ## A container on ANOTHER package's object (#21334)
16143+ *
16144+ * The spec names every expanded view `<object>.<key>`: a bare `list` (one
16145+ * that names no key) takes `<object>.default`, a `form` `<object>.form`,
16146+ * and each named member its own key. For a container of the object's own
16147+ * package those are that package's names, and it still expands there. For
16148+ * a container saved under any other name, in another package or in none,
16149+ * on an object a code package owns, they are the OTHER package's names:
16150+ * ADR-0005 keys an overlay by its own name, and ADR-0126 rules out a silent
16151+ * override, so a row named `x` may not replace an item named
16152+ * `<object>.<key>`. Both callers set each expansion by name — the list's
1615216153 * inline pass over the merged items, the registry's bare key — so the
16153- * expansion used to replace the packaged default on the object door
16154- * (and, on an unscoped kernel, on the by-name read too), wearing the
16155- * shadowed artifact's `_packageId` and protection.
16156- *
16157- * So, on another package's object, the bare `list` expands under the
16158- * container's own name: `<object>.<container name>`, the spec's own
16159- * spelling of a `list` that names its key. The qualified form is the one
16160- * the spec accepts for a ViewItem (`ViewItemNameSchema`); the flat
16161- * container name on an expanded item is refused there, so the list door
16162- * would serve it badged invalid. The view container contract
16163- * (`view.zod.ts`, ADR-0017 §3.2) names the container after its object and
16164- * states no arm for a name another package owns, which is why the arm
16165- * taken is the container's own name rather than a refusal at save.
16154+ * expansion used to replace the packaged view on the object door (and, on
16155+ * an unscoped kernel, on the by-name read too), wearing the shadowed
16156+ * artifact's `_packageId` and protection.
16157+ *
16158+ * So, on another package's object, every name the container expands
16159+ * derives from its own name (triage's ruling, and the seat's answer that
16160+ * extends it to the keyed members): the bare `list` is
16161+ * `<object>.<container name>`, and every other member is
16162+ * `<object>.<container name>.<key>` — the spec's own key rule and its
16163+ * in-container de-duplication, run under the container's name (see
16164+ * {@link expandUnderOwnName}). The qualified forms are the ones the spec
16165+ * accepts for a ViewItem (`ViewItemNameSchema`); the flat container name on
16166+ * an expanded item is refused there, so the list door would serve it
16167+ * badged invalid. The view container contract (`view.zod.ts`, ADR-0017
16168+ * §3.2) names the container after its object and states no arm for a name
16169+ * another package owns, which is why the arm taken is the container's own
16170+ * name rather than a refusal at save.
16171+ *
16172+ * Such a container adds views to the object; it never declares the
16173+ * object's default, so none of its views carries `isDefault` — the
16174+ * switcher's default stays the owning package's (the by-name override and
16175+ * a user's own saved default are the routes that change it).
1616616176 *
1616716177 * Every expanded item carries the container's OWN package and, where that
1616816178 * package ships an artifact of the same name, that artifact's envelope —
@@ -16183,23 +16193,25 @@ export class ObjectStackProtocolImplementation implements
1618316193 ?? (typeof container.name === 'string' ? container.name : undefined);
1618416194 if (!viewObject) return [];
1618516195 const ownPackageId = this.runtimeViewContainerPackage(type, container, options);
16186- const ownKey = this.isAnotherPackagesObject(viewObject, ownPackageId)
16187- ? this.bareListOwnKey(container)
16188- : undefined;
16196+ const crossPackage = this.isAnotherPackagesObject(viewObject, ownPackageId);
16197+ const expanded: ReadonlyArray<Record<string, unknown>> = crossPackage
16198+ ? this.expandUnderOwnName(type, viewObject, container, ownPackageId)
16199+ : (expandViewContainer(viewObject, container) as unknown as Record<string, unknown>[]);
1618916200 const out: Record<string, unknown>[] = [];
16190- for (const vi of expandViewContainer(viewObject, this.withBareListKey(container, ownKey)) ) {
16201+ for (const vi of expanded ) {
1619116202 // Carry the container's package provenance onto each expanded item
1619216203 // so the package-disable filter and ADR-0048 artifact scoping judge
1619316204 // them by the same owner the container has.
16194- const item: Record<string, unknown> = { ...(vi as any) };
16195- if (typeof ownKey === 'string') this.restoreAuthoredBareList(item, viewObject, ownKey);
16205+ const item: Record<string, unknown> = { ...vi };
16206+ // [#21334] Not the object's default: its owning package's is.
16207+ if (crossPackage) delete item.isDefault;
1619616208 if (ownPackageId !== undefined) item._packageId = ownPackageId;
1619716209 // [#21334] Only the container's own package's artifact lends its
1619816210 // envelope; another package's artifact of this name is not this
1619916211 // item's to wear.
1620016212 const viArtifact = ownPackageId === undefined
1620116213 ? undefined
16202- : this.lookupArtifactItem(type, vi .name, ownPackageId);
16214+ : this.lookupArtifactItem(type, String(item .name) , ownPackageId);
1620316215 const ownArtifact = (viArtifact as { _packageId?: unknown } | undefined)?._packageId === ownPackageId
1620416216 ? viArtifact
1620516217 : undefined;
@@ -16235,7 +16247,7 @@ export class ObjectStackProtocolImplementation implements
1623516247 * `ownPackageId`. The discriminator is `getPackagedObjectOwner`, the same
1623616248 * "does a code package ship this?" test {@link classifyObjectContribution}
1623716249 * asks. A runtime-authored object has no packaged owner, so a container on
16238- * one keeps today's `<object>.default` ; so does a registry that cannot
16250+ * one keeps today's `<object>.<key>` names ; so does a registry that cannot
1623916251 * answer.
1624016252 */
1624116253 private isAnotherPackagesObject(object: string, ownPackageId: string | undefined): boolean {
@@ -16248,52 +16260,68 @@ export class ObjectStackProtocolImplementation implements
1624816260 }
1624916261
1625016262 /**
16251- * [#21334] The key a container's bare `list` takes when it may not take
16252- * `default`: the container's own name. `undefined` when the container has
16253- * no bare `list` (none, or one that names its own key, which already
16254- * expands under the name its author gave it). `null` when it has one but no
16255- * name of its own to expand under, so the bare `list` is not expanded at
16256- * all rather than under another package's name.
16257- */
16258- private bareListOwnKey(container: Record<string, any>): string | null | undefined {
16259- const list = container.list;
16260- if (!list || typeof list !== 'object') return undefined;
16261- if (typeof list.name === 'string' && list.name !== '') return undefined;
16262- return typeof container.name === 'string' && container.name !== '' ? container.name : null;
16263- }
16264-
16265- /**
16266- * [#21334] The container the spec expands: unchanged when `ownKey` is
16267- * `undefined`; its bare `list` naming `ownKey`, so the spec's own key rule
16268- * (and its in-container de-duplication) spells `<object>.<ownKey>`; or, for
16269- * `null`, without the bare `list`. A shallow copy — the caller's container
16270- * is never mutated.
16263+ * [#21334] Expand a container on another package's object under its own
16264+ * name. The spec's expander runs with `<object>.<container name>` as its
16265+ * base, so every member it knows — today a named `list`, `listViews`,
16266+ * `formViews`, `form` — comes out as `<object>.<container name>.<key>`,
16267+ * de-duplicated by the spec's own rule, and a member kind the spec adds
16268+ * later is placed the same way. Each item's `object` is set back to the
16269+ * object it binds.
16270+ *
16271+ * The bare `list` is lent the container's name as its key, then served as
16272+ * `<object>.<container name>` itself, its `config` the list as authored.
16273+ * Where the owning package ships that very name (a container named after
16274+ * one of that package's keys), it stays at the spelling the spec gave it,
16275+ * `<object>.<container name>.<container name>`, so it never takes the
16276+ * packaged view's name.
16277+ *
16278+ * A container with no name of its own has nothing to expand under, and
16279+ * expands nothing.
1627116280 */
16272- private withBareListKey(
16281+ private expandUnderOwnName(
16282+ type: string,
16283+ object: string,
1627316284 container: Record<string, any>,
16274- ownKey: string | null | undefined,
16275- ): Record<string, any> {
16276- if (ownKey === undefined) return container;
16277- if (ownKey === null) return { ...container, list: undefined };
16278- return { ...container, list: { ...container.list, name: ownKey } };
16285+ ownPackageId: string | undefined,
16286+ ): Record<string, unknown>[] {
16287+ const ownName = typeof container.name === 'string' && container.name !== '' ? container.name : undefined;
16288+ if (ownName === undefined) return [];
16289+ const under = `${object}.${ownName}`;
16290+ const expandAt = under;
16291+ const list = container.list;
16292+ const bare = !!list && typeof list === 'object' && !(typeof list.name === 'string' && list.name !== '');
16293+ const source = bare ? { ...container, list: { ...list, name: ownName } } : container;
16294+ const bareSpelled = `${expandAt}.${ownName}`;
16295+ const underIsShipped = this.isShippedByAnotherPackage(type, under, ownPackageId);
16296+ return expandViewContainer(expandAt, source).map((vi) => {
16297+ const item: Record<string, any> = { ...vi, object };
16298+ const name = String(item.name);
16299+ const fromBare = bare
16300+ && item.viewKind === 'list'
16301+ && item.config?.name === ownName
16302+ && name.startsWith(bareSpelled)
16303+ && /^(_\d+)?$/.test(name.slice(bareSpelled.length));
16304+ if (fromBare) {
16305+ const authored = { ...item.config };
16306+ delete authored.name;
16307+ item.config = authored;
16308+ if (!underIsShipped) {
16309+ item.name = under;
16310+ delete item._diagnostics;
16311+ }
16312+ }
16313+ return item;
16314+ });
1627916315 }
1628016316
1628116317 /**
16282- * [#21334] Take back the `name` {@link withBareListKey} lent the bare
16283- * `list`, so the expanded item's `config` is the list as authored. Matches
16284- * only the item that key produced: a `list` item whose `config.name` is
16285- * `ownKey` and whose name is `<object>.<ownKey>` or the spec's
16286- * de-duplicated `<object>.<ownKey>_N`.
16318+ * [#21334] True when a code package other than `ownPackageId` ships an
16319+ * artifact named `name` — the one case where the bare list's own name is
16320+ * not free to take.
1628716321 */
16288- private restoreAuthoredBareList(item: Record<string, unknown>, object: string, ownKey: string): void {
16289- const config = item.config as Record<string, unknown> | undefined;
16290- if (item.viewKind !== 'list' || !config || config.name !== ownKey) return;
16291- const requested = `${object}.${ownKey}`;
16292- const name = String(item.name);
16293- if (!name.startsWith(requested) || !/^(_\d+)?$/.test(name.slice(requested.length))) return;
16294- const authored = { ...config };
16295- delete authored.name;
16296- item.config = authored;
16322+ private isShippedByAnotherPackage(type: string, name: string, ownPackageId: string | undefined): boolean {
16323+ const shipped = (this.lookupArtifactItem(type, name) as { _packageId?: unknown } | undefined)?._packageId;
16324+ return typeof shipped === 'string' && shipped !== '' && shipped !== ownPackageId;
1629716325 }
1629816326
1629916327 /**
0 commit comments