Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions packages/base/card-api.gts
Original file line number Diff line number Diff line change
Expand Up @@ -4787,18 +4787,31 @@ function getStore(instance: BaseDef): CardStore {
return stores.get(instance as BaseDef) ?? new FallbackCardStore();
}

// The VirtualNetwork associated with an instance's store, for prefix/RRI
// resolution outside this module. Returns undefined when the instance is
// detached (no store, no loader-attached VN) — callers handle that by
// degrading to URL math or throwing.
export function virtualNetworkFor(
instance: BaseDef,
): VirtualNetwork | undefined {
// Resolve a (possibly relative or RRI) reference to a real, fetchable URL,
// relative to the instance's own location. Card definitions that must hand a
// real URL to a boundary that can't consume canonical RRI (an `<img src>`, the
// AI source-file reader's `new URL(...)`) use this rather than reaching for the
// VirtualNetwork object directly — they get back a URL, not the network itself.
// Resolves through the active Loader's VirtualNetwork (the network boundary);
// returns undefined when none is available (detached / static-parse contexts)
// so callers can degrade to URL math.
export function resolveInstanceURL(
instance: CardDef,
reference: string,
): URL | undefined {
let virtualNetwork: VirtualNetwork | undefined;
try {
return getStore(instance).virtualNetwork;
virtualNetwork = myLoader().getVirtualNetwork();
} catch {
return undefined;
}
if (!virtualNetwork) {
return undefined;
}
return virtualNetwork.resolveURL(
reference,
instance.id ?? instance[relativeTo],
);
}

// Resolve a (possibly relative) reference to its absolute canonical RRI,
Expand Down
34 changes: 13 additions & 21 deletions packages/base/spec.gts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import {
field,
Component,
CardDef,
relativeTo,
linksToMany,
FieldDef,
containsMany,
getCardMeta,
virtualNetworkFor,
resolveInstanceURL,
type CardOrFieldTypeIcon,
BaseDef,
type CardContext,
Expand Down Expand Up @@ -81,23 +80,20 @@ class PopulateFieldSpecExampleCommand extends PopulateWithSampleDataCommand {
if (!codeRef) {
return [];
}
// The attached-file identifiers are read as fetchable source URLs (the AI
// source-file reader does `new URL(...)`), so this must resolve to a real
// URL — keep the VirtualNetwork here (a scoped RRI can't be fetched).
let vn = virtualNetworkFor(card);
if (!vn) {
return [];
}
// The attached-file identifier is read as a fetchable source URL (the AI
// source-file reader does `new URL(...)`), so the card's type module must
// resolve to a real URL — a scoped RRI can't be fetched.
codeRef = codeRefWithAbsoluteIdentifier(
codeRef,
vn.toURL(card.id!),
card.id,
undefined,
vn,
)! as ResolvedCodeRef;
let cardOrFieldModuleURL = codeRef.module
? ensureExtension(codeRef.module, { default: '.gts' })
let moduleURL = codeRef.module
? resolveInstanceURL(card, codeRef.module)
: undefined;
return cardOrFieldModuleURL ? [cardOrFieldModuleURL] : [];
return moduleURL
? [ensureExtension(moduleURL.href, { default: '.gts' })]
: [];
}
}

Expand Down Expand Up @@ -941,13 +937,9 @@ export class Spec extends CardDef {
}
// `moduleHref` is consumed as a fetchable / absolute URL (source reader's
// `new URL(...)`, and URL-form comparisons in the code submode), so it
// must resolve to a real URL — keep the VirtualNetwork here (RRI space
// would leave a scoped prefix that those readers can't use).
let vn = virtualNetworkFor(this);
if (!vn) {
return undefined;
}
return vn.resolveURL(this.ref.module, this.id ?? this[relativeTo]).href;
// must resolve to a real URL — RRI space would leave a scoped prefix that
// those readers can't use.
return resolveInstanceURL(this, this.ref.module)?.href;
},
});
@field linkedExamples = linksToMany(CardDef);
Expand Down
39 changes: 13 additions & 26 deletions packages/experiments-realm/asset.gts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import {
CardDef,
FieldDef,
Component,
relativeTo,
virtualNetworkFor,
resolveInstanceURL,
} from 'https://cardstack.com/base/card-api';
import StringField from 'https://cardstack.com/base/string';
import CurrencyIcon from '@cardstack/boxel-icons/currency';
Expand All @@ -21,19 +20,13 @@ export class Asset extends CardDef {
if (!this.logoURL) {
return null;
}
let rel = this[relativeTo] || this.id;
// The instance may have no store-attached VirtualNetwork (detached
// / static-parse contexts), and `rel` may be a prefix-form RRI
// (e.g. `@cardstack/…/Asset/foo`) that `new URL()` can't parse on
// its own. If we can't resolve a base, return `logoURL` raw so the
// <img src> binding still has a string to render rather than
// letting the compute throw.
// `logoURL` may be relative to the instance, which lives at a prefix-form
// RRI (e.g. `@cardstack/…/Asset/foo`) that `new URL()` can't parse.
// Resolve it against the instance's real URL for the <img src>; if no
// VirtualNetwork is available (detached / static-parse), fall back to the
// raw value.
try {
let base =
typeof rel === 'string'
? (virtualNetworkFor(this)?.toURL(rel) ?? new URL(rel))
: rel;
return new URL(this.logoURL, base).href;
return resolveInstanceURL(this, this.logoURL)?.href ?? this.logoURL;
} catch {
return this.logoURL;
}
Expand Down Expand Up @@ -105,19 +98,13 @@ class AssetField extends FieldDef {
if (!this.logoURL) {
return null;
}
let rel = this[relativeTo] || this.id;
// The instance may have no store-attached VirtualNetwork (detached
// / static-parse contexts), and `rel` may be a prefix-form RRI
// (e.g. `@cardstack/…/Asset/foo`) that `new URL()` can't parse on
// its own. If we can't resolve a base, return `logoURL` raw so the
// <img src> binding still has a string to render rather than
// letting the compute throw.
// `logoURL` may be relative to the instance, which lives at a prefix-form
// RRI (e.g. `@cardstack/…/Asset/foo`) that `new URL()` can't parse.
// Resolve it against the instance's real URL for the <img src>; if no
// VirtualNetwork is available (detached / static-parse), fall back to the
// raw value.
try {
let base =
typeof rel === 'string'
? (virtualNetworkFor(this)?.toURL(rel) ?? new URL(rel))
: rel;
return new URL(this.logoURL, base).href;
return resolveInstanceURL(this, this.logoURL)?.href ?? this.logoURL;
} catch {
return this.logoURL;
}
Expand Down
Loading