Skip to content

Commit 0ed97db

Browse files
committed
Merge branch 'main' into event-processing-restartability
2 parents 1acc62b + 4ecd379 commit 0ed97db

File tree

3 files changed

+149
-8
lines changed

3 files changed

+149
-8
lines changed

packages/base/code-ref.gts

+21-7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ import {
1616
import { ResolvedCodeRef } from '@cardstack/runtime-common';
1717
import CodeIcon from '@cardstack/boxel-icons/code';
1818

19+
function moduleIsUrlLike(module: string) {
20+
return (
21+
module.startsWith('http') ||
22+
module.startsWith('.') ||
23+
module.startsWith('/')
24+
);
25+
}
26+
1927
class BaseView extends Component<typeof CodeRefField> {
2028
<template>
2129
<div data-test-ref>
@@ -39,7 +47,9 @@ export default class CodeRefField extends FieldDef {
3947
) {
4048
return {
4149
...codeRef,
42-
...(opts?.maybeRelativeURL && !opts?.useAbsoluteURL
50+
...(opts?.maybeRelativeURL &&
51+
!opts?.useAbsoluteURL &&
52+
moduleIsUrlLike(codeRef.module)
4353
? { module: opts.maybeRelativeURL(codeRef.module) }
4454
: {}),
4555
};
@@ -70,12 +80,16 @@ function maybeSerializeCodeRef(
7080
stack: CardDef[] = [],
7181
) {
7282
if (codeRef) {
73-
// if a stack is passed in, use the containing card to resolve relative references
74-
let moduleHref =
75-
stack.length > 0
76-
? new URL(codeRef.module, stack[0][relativeTo]).href
77-
: codeRef.module;
78-
return `${moduleHref}/${codeRef.name}`;
83+
if (moduleIsUrlLike(codeRef.module)) {
84+
// if a stack is passed in, use the containing card to resolve relative references
85+
let moduleHref =
86+
stack.length > 0
87+
? new URL(codeRef.module, stack[0][relativeTo]).href
88+
: codeRef.module;
89+
return `${moduleHref}/${codeRef.name}`;
90+
} else {
91+
return `${codeRef.module}/${codeRef.name}`;
92+
}
7993
}
8094
return undefined;
8195
}

packages/base/skill-card.gts

+47-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,57 @@
1+
import {
2+
CardDef,
3+
Component,
4+
FieldDef,
5+
field,
6+
contains,
7+
containsMany,
8+
} from './card-api';
9+
import BooleanField from './boolean';
10+
import CodeRefField from './code-ref';
111
import MarkdownField from './markdown';
2-
import { CardDef, Component, field, contains } from './card-api';
12+
import StringField from './string';
313
import RobotIcon from '@cardstack/boxel-icons/robot';
14+
import { simpleHash } from '@cardstack/runtime-common';
15+
16+
function friendlyModuleName(fullModuleUrl: string) {
17+
return fullModuleUrl
18+
.split('/')
19+
.pop()!
20+
.replace(/\.gts$/, '');
21+
}
22+
23+
export class CommandField extends FieldDef {
24+
static displayName = 'CommandField';
25+
@field codeRef = contains(CodeRefField, {
26+
description: 'An absolute code reference to the command to be executed',
27+
});
28+
@field requiresApproval = contains(BooleanField, {
29+
description:
30+
'If true, this command will require human approval before it is executed in the host.',
31+
});
32+
33+
@field functionName = contains(StringField, {
34+
description: 'The name of the function to be executed',
35+
computeVia: function (this: CommandField) {
36+
if (!this.codeRef?.module || !this.codeRef?.name) {
37+
return '';
38+
}
39+
40+
const hashed = simpleHash(`${this.codeRef.module}#${this.codeRef.name}`);
41+
let name =
42+
this.codeRef.name === 'default'
43+
? friendlyModuleName(this.codeRef.module)
44+
: this.codeRef.name;
45+
return `${name}_${hashed.slice(0, 4)}`;
46+
},
47+
});
48+
}
449

550
export class SkillCard extends CardDef {
651
static displayName = 'Skill';
752
static icon = RobotIcon;
853
@field instructions = contains(MarkdownField);
54+
@field commands = containsMany(CommandField);
955
static embedded = class Embedded extends Component<typeof this> {
1056
<template>
1157
<@fields.title />

packages/host/tests/integration/realm-indexing-and-querying-test.gts

+81
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,27 @@ module(`Integration | realm indexing and querying`, function (hooks) {
734734
},
735735
},
736736
},
737+
'people-skill.json': {
738+
data: {
739+
attributes: {
740+
instructions: 'How to win friends and influence people',
741+
commands: [
742+
{
743+
codeRef: {
744+
module: `@cardstack/boxel-host/commands/switch-submode`,
745+
name: 'default',
746+
},
747+
},
748+
],
749+
},
750+
meta: {
751+
adoptsFrom: {
752+
module: 'https://cardstack.com/base/skill-card',
753+
name: 'SkillCard',
754+
},
755+
},
756+
},
757+
},
737758
},
738759
});
739760
let indexer = realm.realmIndexQueryEngine;
@@ -807,6 +828,66 @@ module(`Integration | realm indexing and querying`, function (hooks) {
807828
`search entry was an error: ${entry?.error.errorDetail.message}`,
808829
);
809830
}
831+
entry = await indexer.cardDocument(new URL(`${testRealmURL}people-skill`));
832+
if (entry?.type === 'doc') {
833+
assert.deepEqual(entry.doc.data, {
834+
id: `${testRealmURL}people-skill`,
835+
type: 'card',
836+
links: {
837+
self: `${testRealmURL}people-skill`,
838+
},
839+
attributes: {
840+
commands: [
841+
{
842+
codeRef: {
843+
module: '@cardstack/boxel-host/commands/switch-submode',
844+
name: 'default',
845+
},
846+
functionName: 'switch-submode_dd88',
847+
requiresApproval: null,
848+
},
849+
],
850+
description: null,
851+
instructions: 'How to win friends and influence people',
852+
thumbnailURL: null,
853+
title: null,
854+
},
855+
meta: {
856+
adoptsFrom: {
857+
module: 'https://cardstack.com/base/skill-card',
858+
name: 'SkillCard',
859+
},
860+
lastModified: adapter.lastModifiedMap.get(
861+
`${testRealmURL}people-skill.json`,
862+
),
863+
resourceCreatedAt: adapter.resourceCreatedAtMap.get(
864+
`${testRealmURL}people-skill.json`,
865+
),
866+
realmInfo: testRealmInfo,
867+
realmURL: testRealmURL,
868+
},
869+
});
870+
let instance = await indexer.instance(
871+
new URL(`${testRealmURL}people-skill`),
872+
);
873+
assert.deepEqual(instance?.searchDoc, {
874+
_cardType: 'Skill',
875+
id: `${testRealmURL}people-skill`,
876+
instructions: 'How to win friends and influence people',
877+
commands: [
878+
{
879+
codeRef: `@cardstack/boxel-host/commands/switch-submode/default`,
880+
functionName: 'switch-submode_dd88',
881+
requiresApproval: false,
882+
},
883+
],
884+
});
885+
} else {
886+
assert.ok(
887+
false,
888+
`search entry was an error: ${entry?.error.errorDetail.message}`,
889+
);
890+
}
810891
});
811892

812893
test('can recover from rendering a card that has a template error', async function (assert) {

0 commit comments

Comments
 (0)