Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
db027aa
Add preliminary conversion to import maps
backspace Mar 13, 2026
570b03a
Update to allow @cardstack/catalog in cards
backspace Mar 16, 2026
44df01a
Add formatting autofix
backspace Mar 16, 2026
f1c9fa2
Merge remote-tracking branch 'origin/main' into cs-10440-change-to-us…
backspace Mar 16, 2026
c68929c
Remove unresolution in definition lookup
backspace Mar 18, 2026
79231a0
Fix resolution in catalog
backspace Mar 18, 2026
b0b79d6
Fix some URL constructions
backspace Mar 18, 2026
36281db
Add @cardstack/skills universal reference
backspace Mar 18, 2026
e62270d
Merge branch 'main' into cs-10440-change-to-use-import-maps-for-cards…
backspace Mar 19, 2026
33946e7
Merge remote-tracking branch 'origin/cs-10440-change-to-use-import-ma…
backspace Mar 19, 2026
a81cbe4
Merge remote-tracking branch 'origin/main' into @cardstack/skills-cs-…
backspace Mar 19, 2026
fd82737
Add more places to handle universal references
backspace Mar 19, 2026
84c55e1
Merge remote-tracking branch 'origin/main' into @cardstack/skills-cs-…
backspace Mar 19, 2026
dd0a05e
Merge branch 'main' into cs-10440-change-to-use-import-maps-for-cards…
backspace Mar 20, 2026
bb10303
Merge branch 'cs-10440-change-to-use-import-maps-for-cardstackcatalog…
backspace Mar 20, 2026
7100b76
Add more URL-conversion wrappers
backspace Mar 20, 2026
5f72197
Merge remote-tracking branch 'origin/main' into @cardstack/skills-cs-…
backspace Mar 23, 2026
48c6eb4
Remove unused import
backspace Mar 23, 2026
152f9dc
Add more mappings
backspace Mar 23, 2026
0ddda71
Fix interpretation of absolute as relative
backspace Mar 23, 2026
0ce62a3
Add another relative resolution fix
backspace Mar 23, 2026
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
2 changes: 1 addition & 1 deletion mise-tasks/services/realm-server
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ LOW_CREDIT_THRESHOLD="${LOW_CREDIT_THRESHOLD:-2000}" \
\
--path='../skills-realm/contents' \
--username='skills_realm' \
--fromUrl="${REALM_BASE_URL}/skills/" \
--fromUrl='@cardstack/skills/' \
--toUrl="${REALM_BASE_URL}/skills/" \
\
${START_SUBMISSION:+--path="${SUBMISSION_REALM_PATH}"} \
Expand Down
2 changes: 1 addition & 1 deletion mise-tasks/services/worker
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ NODE_ENV=development \
${START_CATALOG:+--fromUrl='@cardstack/catalog/'} \
${START_CATALOG:+--toUrl="${CATALOG_REALM_URL}"} \
\
--fromUrl="${REALM_BASE_URL}/skills/" \
--fromUrl='@cardstack/skills/' \
--toUrl="${REALM_BASE_URL}/skills/" \
\
${START_CATALOG:+--fromUrl="${EXTERNAL_CATALOG_REALM_URL}"} \
Expand Down
2 changes: 1 addition & 1 deletion mise-tasks/services/worker-test
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@ NODE_ENV=test \
--toUrl="${REALM_TEST_URL}/test/" \
--fromUrl='https://cardstack.com/base/' \
--toUrl="${REALM_BASE_URL}/base/" \
--fromUrl="${REALM_BASE_URL}/skills/" \
--fromUrl='@cardstack/skills/' \
--toUrl="${REALM_BASE_URL}/skills/"
10 changes: 5 additions & 5 deletions packages/base/card-api.gts
Original file line number Diff line number Diff line change
Expand Up @@ -2832,7 +2832,7 @@ function lazilyLoadLink(
fieldValue = (await createFromSerialized(
fileMetaDoc.data,
fileMetaDoc,
new URL(fileMetaDoc.data.id!),
cardIdToURL(fileMetaDoc.data.id!),
{ store, dependencyTrackingContext },
)) as FileDef;
} else {
Expand All @@ -2848,7 +2848,7 @@ function lazilyLoadLink(
fieldValue = (await createFromSerialized(
cardDoc.data,
cardDoc,
new URL(cardDoc.data.id!),
cardIdToURL(cardDoc.data.id!),
{ store, dependencyTrackingContext },
)) as CardDef;
}
Expand Down Expand Up @@ -3173,7 +3173,7 @@ export async function updateFromSerialized<T extends BaseDefConstructor>(
): Promise<BaseInstanceType<T>> {
stores.set(instance, store);
if (!instance[relativeTo] && doc.data.id) {
instance[relativeTo] = new URL(doc.data.id);
instance[relativeTo] = cardIdToURL(doc.data.id);
}

if (isCardInstance(instance)) {
Expand Down Expand Up @@ -3299,7 +3299,7 @@ async function _updateFromSerialized<T extends BaseDefConstructor>({
let instanceRelativeTo =
instance[relativeTo] ??
('id' in instance && typeof instance.id === 'string'
? new URL(instance.id)
? cardIdToURL(instance.id)
: undefined);

function getFieldMeta(
Expand Down Expand Up @@ -3482,7 +3482,7 @@ async function _updateFromSerialized<T extends BaseDefConstructor>({
let relativeToVal =
instance[relativeTo] ??
('id' in instance && typeof instance.id === 'string'
? new URL(instance.id)
? cardIdToURL(instance.id)
: undefined);
let deserializedValue = await getDeserializedValue({
card,
Expand Down
3 changes: 2 additions & 1 deletion packages/base/cards-grid.gts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Captions from '@cardstack/boxel-icons/captions';
import AllCardsIcon from '@cardstack/boxel-icons/square-stack';

import {
cardIdToURL,
chooseCard,
specRef,
baseRealm,
Expand Down Expand Up @@ -258,7 +259,7 @@ class Isolated extends Component<typeof CardsGrid> {
}

if (spec && isCardInstance<Spec>(spec)) {
await this.args.createCard?.(spec.ref, new URL(spec.id!), {
await this.args.createCard?.(spec.ref, cardIdToURL(spec.id!), {
realmURL: this.args.model[realmURL],
});
}
Expand Down
3 changes: 2 additions & 1 deletion packages/base/query-field-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
RuntimeDependencyTrackingContext,
} from '@cardstack/runtime-common';
import {
cardIdToURL,
getField,
getSingularRelationship,
identifyCard,
Expand Down Expand Up @@ -361,7 +362,7 @@ function resolveQueryAndRealm(
fieldPath,
resolvePathValue: (path) => resolveInstancePathValue(instance, path),
relativeTo: (instance as CardDef).id
? new URL((instance as CardDef).id)
? cardIdToURL((instance as CardDef).id)
: realmURL,
});

Expand Down
4 changes: 2 additions & 2 deletions packages/host/app/lib/realm-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RealmPaths } from '@cardstack/runtime-common';
import { cardIdToURL, RealmPaths } from '@cardstack/runtime-common';

/**
* Normalizes realm URLs by ensuring they have trailing slashes and
Expand Down Expand Up @@ -33,7 +33,7 @@ export function normalizeRealms(realms: string[]): string[] {
* // Returns: 'http://localhost:4201/test/'
*/
export function resolveCardRealmUrl(cardId: string, realms: string[]): string {
let cardUrl = new URL(cardId);
let cardUrl = cardIdToURL(cardId);
for (let realm of realms) {
let realmUrl = new URL(realm);
let realmPaths = new RealmPaths(realmUrl);
Expand Down
13 changes: 6 additions & 7 deletions packages/host/app/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,18 +155,17 @@ export const catalogRealm = ENV.resolvedCatalogRealmURL
export const skillsRealm = new RealmPaths(new URL(ENV.resolvedSkillsRealmURL));

/**
* Safely constructs a URL to a skill card in the skills realm.
* Uses the URL constructor to handle path joining safely.
* Constructs a universal @cardstack/skills/ reference to a skill card.
*
* @param skillId - The ID of the skill (e.g., 'boxel-environment', 'catalog-listing')
* @returns The complete URL to the skill card
* @returns The universal skill card reference
*
* @example
* skillCardURL('catalog-listing') // 'http://localhost:4201/skills/Skill/catalog-listing'
* skillCardURL('catalog-listing') // '@cardstack/skills/Skill/catalog-listing'
*/
export function skillCardURL(skillId: string): string {
return skillsRealm.fileURL(`Skill/${skillId}`).href;
return `@cardstack/skills/Skill/${skillId}`;
}

export const devSkillId = skillsRealm.fileURL(devSkillLocalPath).href;
export const envSkillId = skillsRealm.fileURL(envSkillLocalPath).href;
export const devSkillId = `@cardstack/skills/${devSkillLocalPath}`;
export const envSkillId = `@cardstack/skills/${envSkillLocalPath}`;
3 changes: 2 additions & 1 deletion packages/host/app/resources/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import difference from 'lodash/difference';
import { TrackedMap } from 'tracked-built-ins';

import {
cardIdToURL,
isCardInstance,
type LooseSingleCardDocument,
} from '@cardstack/runtime-common';
Expand Down Expand Up @@ -317,7 +318,7 @@ export class RoomResource extends Resource<Args> {
for (let skillCard of this.allSkillFileDefs) {
result.push({
cardId: skillCard.sourceUrl,
realmURL: this.realm.realmOfURL(new URL(skillCard.sourceUrl))?.href,
realmURL: this.realm.realmOfURL(cardIdToURL(skillCard.sourceUrl))?.href,
fileDef: skillCard,
isActive:
this.matrixRoom?.skillsConfig.enabledSkillCards
Expand Down
2 changes: 1 addition & 1 deletion packages/host/app/routes/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ export default class RenderRoute extends Route<Model> {
let instance = (await this.store.addFileMeta(
resource,
doc,
resource.id ? new URL(resource.id) : undefined,
resource.id ? cardIdToURL(resource.id) : undefined,
)) as unknown as CardDef;

let state = new TrackedMap<string, unknown>();
Expand Down
8 changes: 8 additions & 0 deletions packages/host/app/services/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ export default class NetworkService extends Service {
(rest) => new URL(rest, catalogURL).href,
);
}
if (config.resolvedSkillsRealmURL) {
let skillsURL = withTrailingSlash(config.resolvedSkillsRealmURL);
registerCardReferencePrefix('@cardstack/skills/', skillsURL);
virtualNetwork.addImportMap(
'@cardstack/skills/',
(rest) => new URL(rest, skillsURL).href,
);
}
if (config.resolvedOpenRouterRealmURL) {
let openRouterURL = withTrailingSlash(config.resolvedOpenRouterRealmURL);
registerCardReferencePrefix('@cardstack/openrouter/', openRouterURL);
Expand Down
4 changes: 2 additions & 2 deletions packages/host/app/services/operator-mode-state-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import { TrackedArray, TrackedMap, TrackedObject } from 'tracked-built-ins';

import type { CodeRef } from '@cardstack/runtime-common';
import {
cardIdToURL,
RealmPaths,
type LocalPath,
cardIdToURL,
isResolvedCodeRef,
isCardInstance,
isLocalId,
Expand Down Expand Up @@ -312,7 +312,7 @@ export default class OperatorModeStateService extends Service {
this.trimItemsFromStack(item);
}
let realmPaths = new RealmPaths(new URL(cardRealmUrl));
let cardPath = realmPaths.local(new URL(`${cardId}.json`));
let cardPath = realmPaths.local(cardIdToURL(`${cardId}.json`));
this.recentFilesService.removeRecentFile(cardPath);
this.recentCardsService.remove(cardId);
}
Expand Down
15 changes: 9 additions & 6 deletions packages/host/app/services/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
baseFileRef,
CardError,
cardIdToURL,
isRegisteredPrefix,
hasExecutableExtension,
isCardError,
isCardInstance,
Expand Down Expand Up @@ -1312,7 +1313,7 @@ export default class StoreService extends Service implements StoreInterface {
return this.createFileMetaFromSerialized(
resource,
doc,
new URL(resource.id),
cardIdToURL(resource.id),
dependencyTrackingContext,
) as Promise<T>;
}
Expand Down Expand Up @@ -1416,7 +1417,7 @@ export default class StoreService extends Service implements StoreInterface {
deferred?.fulfill(existingInstance as T | CardErrorJSONAPI);
return existingInstance as T;
}
if (isLocalId(id)) {
if (isLocalId(id) && !isRegisteredPrefix(id)) {
// we might have lost the local id via a loader refresh, try loading from remote id instead
let remoteId = this.store.getRemoteIds(id)?.[0];
if (!remoteId) {
Expand All @@ -1426,7 +1427,9 @@ export default class StoreService extends Service implements StoreInterface {
}
id = remoteId;
}
let url = id; // after this point we know we are dealing with a remote id, e.g. url
// Resolve registered prefix IDs (e.g. @cardstack/skills/...) to actual
// URLs so they can be used for fetching.
let url = isRegisteredPrefix(id) ? cardIdToURL(id).href : id;
let doc = (typeof idOrDoc !== 'string' ? idOrDoc : undefined) as
| SingleCardDocument
| undefined;
Expand Down Expand Up @@ -1546,10 +1549,10 @@ export default class StoreService extends Service implements StoreInterface {
deferred.fulfill(existingInstance as T | CardErrorJSONAPI);
return existingInstance as T | CardErrorJSONAPI;
}
if (isLocalId(id)) {
if (isLocalId(id) && !isRegisteredPrefix(id)) {
throw new Error(`file-meta reads do not support local ids (${id})`);
}
let url = id;
let url = isRegisteredPrefix(id) ? cardIdToURL(id).href : id;
let fileMetaDoc: SingleFileMetaDocument | CardError;
if (this.isRenderStore && (globalThis as any).__boxelRenderContext) {
fileMetaDoc = await this.extractFileMetaDirectly(url);
Expand All @@ -1565,7 +1568,7 @@ export default class StoreService extends Service implements StoreInterface {
let fileInstance = await api.createFromSerialized(
fileMetaDoc.data,
fileMetaDoc,
fileMetaDoc.data.id ? new URL(fileMetaDoc.data.id) : new URL(url),
fileMetaDoc.data.id ? cardIdToURL(fileMetaDoc.data.id) : new URL(url),
{
store: this.store,
dependencyTrackingContext: opts?.dependencyTrackingContext,
Expand Down
6 changes: 5 additions & 1 deletion packages/matrix/helpers/isolated-realm-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@ export async function startServer({
`--fromUrl='http://localhost:4205/test/'`,
`--toUrl='http://localhost:4205/test/'`,
];
workerArgs = workerArgs.concat([
`--fromUrl='@cardstack/skills/'`,
`--toUrl='http://localhost:4205/skills/'`,
]);
workerArgs = workerArgs.concat([
`--fromUrl='https://cardstack.com/base/'`,
`--toUrl='http://localhost:4205/base/'`,
Expand Down Expand Up @@ -282,7 +286,7 @@ export async function startServer({
serverArgs = serverArgs.concat([
`--username='skills_realm'`,
`--path='${skillsRealmDir}'`,
`--fromUrl='http://localhost:4205/skills/'`,
`--fromUrl='@cardstack/skills/'`,
`--toUrl='http://localhost:4205/skills/'`,
]);
serverArgs = serverArgs.concat([
Expand Down
4 changes: 2 additions & 2 deletions packages/matrix/tests/skills.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ test.describe('Skills', () => {
).toContainClass('checked');
}

const environmentSkillCardId = `http://localhost:4205/skills/Skill/boxel-environment`;
const environmentSkillCardId = `@cardstack/skills/Skill/boxel-environment`;
const defaultSkillCardsForCodeMode = [
`http://localhost:4205/skills/Skill/boxel-development`,
`@cardstack/skills/Skill/boxel-development`,
environmentSkillCardId,
];
const skillCard1 = `${appURL}/skill-pirate-speak`;
Expand Down
2 changes: 1 addition & 1 deletion packages/realm-server/scripts/start-production.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ NODE_NO_WARNINGS=1 \
\
--path='/persistent/skills' \
--username='skills_realm' \
--fromUrl='https://app.boxel.ai/skills/' \
--fromUrl='@cardstack/skills/' \
--toUrl='https://app.boxel.ai/skills/' \
\
--path='/persistent/boxel-homepage' \
Expand Down
2 changes: 1 addition & 1 deletion packages/realm-server/scripts/start-staging.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ NODE_NO_WARNINGS=1 \
\
--path='/persistent/skills' \
--username='skills_realm' \
--fromUrl='https://realms-staging.stack.cards/skills/' \
--fromUrl='@cardstack/skills/' \
--toUrl='https://realms-staging.stack.cards/skills/' \
\
--path='/persistent/boxel-homepage' \
Expand Down
2 changes: 1 addition & 1 deletion packages/realm-server/scripts/start-worker-production.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ NODE_NO_WARNINGS=1 \
--fromUrl='@cardstack/catalog/' \
--toUrl="${CATALOG_REALM_URL}" \
\
--fromUrl='https://app.boxel.ai/skills/' \
--fromUrl='@cardstack/skills/' \
--toUrl='https://app.boxel.ai/skills/' \
\
--fromUrl="${EXTERNAL_CATALOG_REALM_URL}" \
Expand Down
2 changes: 1 addition & 1 deletion packages/realm-server/scripts/start-worker-staging.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ NODE_NO_WARNINGS=1 \
--fromUrl="${EXTERNAL_CATALOG_REALM_URL}" \
--toUrl="${EXTERNAL_CATALOG_REALM_URL}" \
\
--fromUrl='https://realms-staging.stack.cards/skills/' \
--fromUrl='@cardstack/skills/' \
--toUrl='https://realms-staging.stack.cards/skills/' \
\
--fromUrl='@cardstack/openrouter/' \
Expand Down
13 changes: 13 additions & 0 deletions packages/runtime-common/card-reference-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ export function resolveCardReference(
`Cannot resolve bare package specifier "${reference}" — no matching prefix mapping registered`,
);
}
if (reference.startsWith('http://') || reference.startsWith('https://')) {
return new URL(reference).href;
}
// If relativeTo is a prefix-form ID (e.g. @cardstack/skills/Foo/bar),
// resolve it to a real URL before using it as a base.
if (typeof relativeTo === 'string') {
for (let [prefix, target] of prefixMappings) {
if (relativeTo.startsWith(prefix)) {
relativeTo = new URL(relativeTo.slice(prefix.length), target).href;
break;
}
}
}
return new URL(reference, relativeTo).href;
}

Expand Down
Loading