Skip to content

Commit 609d1a4

Browse files
committed
Merge branch 'main' into cs-6860-update-eslint-plugin-ember-version
2 parents d5607f6 + e226f8d commit 609d1a4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+500
-597
lines changed

docs/computed-fields.md

-22
Original file line numberDiff line numberDiff line change
@@ -37,28 +37,6 @@ When any field consumed by the computed field changes, the value will [rerender]
3737
3838
Computed fields are eagerly evaluated, they do not need to be consumed for `computeVia` to run.
3939
40-
## Async computation
41-
42-
The calculation can be async:
43-
44-
```typescript
45-
@field slowName = contains(StringCard, {
46-
computeVia: async function () {
47-
await new Promise((resolve) => setTimeout(resolve, 10));
48-
return this.firstName;
49-
}
50-
});
51-
```
52-
53-
The values of async computed fields are available synchronously in other `computedVia` functions. Since async field values are not guaranteed to be present, you can use card API [`getIfReady`](https://github.com/cardstack/boxel/blob/307d78676ebdb93cee75d61b8812914013a094a7/packages/base/card-api.gts#L2112) avoid errors about the field not being ready:
54-
55-
```javascript
56-
await getIfReady(this, 'fullName');
57-
// 'Carl Stack'
58-
await getIfReady(this, 'fullName');
59-
// {type: 'not-ready', fieldName: 'fullName', instance: …}
60-
```
61-
6240
## Computed `linksTo` and `linksToMany`
6341
6442
While `computeVia` can currently only be applied to `contains`/`containsMany` fields, there’s a plan to let it work for `linksTo` and `linksToMany` in the future.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838
"style-loader@2.0.0": "patches/style-loader@2.0.0.patch",
3939
"ember-css-url@1.0.0": "patches/ember-css-url@1.0.0.patch",
4040
"matrix-js-sdk@31.0.0": "patches/matrix-js-sdk@31.0.0.patch",
41-
"ember-basic-dropdown@8.0.4": "patches/ember-basic-dropdown@8.0.4.patch"
41+
"ember-basic-dropdown@8.0.4": "patches/ember-basic-dropdown@8.0.4.patch",
42+
"ember-source@5.4.1": "patches/ember-source@5.4.1.patch"
4243
}
4344
},
4445
"devDependencies": {

packages/ai-bot/helpers.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,9 @@ export const attachedCardsToMessage = (
253253
history: DiscreteMatrixEvent[],
254254
aiBotUserId: string,
255255
) => {
256-
for (let card of getRelevantCards(history, aiBotUserId)) {
257-
return `Full card data: ${JSON.stringify(card)}`;
258-
}
259-
return;
256+
return `Full card data: ${JSON.stringify(
257+
getRelevantCards(history, aiBotUserId),
258+
)}`;
260259
};
261260

262261
export function cleanContent(content: string) {

packages/ai-bot/tests/prompt-construction-test.ts

+99
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,105 @@ module('getModifyPrompt', () => {
478478
assert.equal(relevantCards.length, 2);
479479
});
480480

481+
test('Gets multiple uploaded cards in the system prompt', () => {
482+
const history: DiscreteMatrixEvent[] = [
483+
{
484+
type: 'm.room.message',
485+
event_id: '1',
486+
origin_server_ts: 1234567890,
487+
content: {
488+
msgtype: 'org.boxel.message',
489+
format: 'org.matrix.custom.html',
490+
body: 'Hey',
491+
formatted_body: 'Hey',
492+
data: {
493+
attachedCards: [
494+
{
495+
data: {
496+
type: 'card',
497+
id: 'http://localhost:4201/drafts/Author/1',
498+
attributes: {
499+
firstName: 'Terry',
500+
lastName: 'Pratchett',
501+
},
502+
meta: {
503+
adoptsFrom: {
504+
module: '../author',
505+
name: 'Author',
506+
},
507+
},
508+
},
509+
},
510+
],
511+
context: {
512+
openCards: [],
513+
tools: [],
514+
submode: 'interact',
515+
},
516+
},
517+
},
518+
sender: '@user:localhost',
519+
room_id: 'room1',
520+
unsigned: {
521+
age: 115498,
522+
transaction_id: '1',
523+
},
524+
},
525+
{
526+
type: 'm.room.message',
527+
event_id: '1',
528+
origin_server_ts: 1234567890,
529+
content: {
530+
msgtype: 'org.boxel.message',
531+
format: 'org.matrix.custom.html',
532+
body: 'Hey',
533+
formatted_body: 'Hey',
534+
data: {
535+
attachedCards: [
536+
{
537+
data: {
538+
type: 'card',
539+
id: 'http://localhost:4201/drafts/Author/2',
540+
attributes: {
541+
firstName: 'Mr',
542+
lastName: 'T',
543+
},
544+
meta: {
545+
adoptsFrom: {
546+
module: '../author',
547+
name: 'Author',
548+
},
549+
},
550+
},
551+
},
552+
],
553+
context: {
554+
openCards: [],
555+
tools: [],
556+
submode: 'interact',
557+
},
558+
},
559+
},
560+
sender: '@user:localhost',
561+
room_id: 'room1',
562+
unsigned: {
563+
age: 115498,
564+
transaction_id: '2',
565+
},
566+
},
567+
];
568+
const fullPrompt = getModifyPrompt(history, '@aibot:localhost');
569+
const systemMessage = fullPrompt.find(
570+
(message) => message.role === 'system',
571+
);
572+
assert.true(
573+
systemMessage?.content?.includes('http://localhost:4201/drafts/Author/1'),
574+
);
575+
assert.true(
576+
systemMessage?.content?.includes('http://localhost:4201/drafts/Author/2'),
577+
);
578+
});
579+
481580
test('If a user stops sharing their context keep it in the system prompt', () => {
482581
const history: DiscreteMatrixEvent[] = [
483582
{

packages/base/card-api.gts

+35-14
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,9 @@ function cardTypeFor(
353353
if (primitive in field.card) {
354354
return field.card;
355355
}
356+
if (boxedElement.value == null) {
357+
return field.card;
358+
}
356359
return Reflect.getPrototypeOf(boxedElement.value)!
357360
.constructor as typeof BaseDef;
358361
}
@@ -448,9 +451,12 @@ class ContainsMany<FieldT extends FieldDefConstructor>
448451
// WatchedArray proxy is not structuredClone-able, and hence cannot be
449452
// communicated over the postMessage boundary between worker and DOM.
450453
// TODO: can this be simplified since we don't have the worker anymore?
451-
return [...instances].map((instance) => {
452-
return this.card[queryableValue](instance, stack);
453-
});
454+
let results = [...instances]
455+
.map((instance) => {
456+
return this.card[queryableValue](instance, stack);
457+
})
458+
.filter((i) => i != null);
459+
return results.length === 0 ? null : results;
454460
}
455461

456462
serialize(
@@ -1138,17 +1144,23 @@ class LinksToMany<FieldT extends CardDefConstructor>
11381144
// WatchedArray proxy is not structuredClone-able, and hence cannot be
11391145
// communicated over the postMessage boundary between worker and DOM.
11401146
// TODO: can this be simplified since we don't have the worker anymore?
1141-
return [...instances].map((instance) => {
1142-
if (primitive in instance) {
1143-
throw new Error(
1144-
`the linksToMany field '${this.name}' contains a primitive card '${instance.name}'`,
1145-
);
1146-
}
1147-
if (isNotLoadedValue(instance)) {
1148-
return { id: instance.reference };
1149-
}
1150-
return this.card[queryableValue](instance, stack);
1151-
});
1147+
let results = [...instances]
1148+
.map((instance) => {
1149+
if (instance == null) {
1150+
return null;
1151+
}
1152+
if (primitive in instance) {
1153+
throw new Error(
1154+
`the linksToMany field '${this.name}' contains a primitive card '${instance.name}'`,
1155+
);
1156+
}
1157+
if (isNotLoadedValue(instance)) {
1158+
return { id: instance.reference };
1159+
}
1160+
return this.card[queryableValue](instance, stack);
1161+
})
1162+
.filter((i) => i != null);
1163+
return results.length === 0 ? null : results;
11521164
}
11531165

11541166
serialize(
@@ -1173,6 +1185,15 @@ class LinksToMany<FieldT extends CardDefConstructor>
11731185

11741186
let relationships: Record<string, Relationship> = {};
11751187
values.map((value, i) => {
1188+
if (value == null) {
1189+
relationships[`${this.name}\.${i}`] = {
1190+
links: {
1191+
self: null,
1192+
},
1193+
data: null,
1194+
};
1195+
return;
1196+
}
11761197
if (isNotLoadedValue(value)) {
11771198
relationships[`${this.name}\.${i}`] = {
11781199
links: {

packages/base/catalog-entry.gts

+2-53
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,13 @@ import {
44
Component,
55
CardDef,
66
FieldDef,
7-
BaseDef,
8-
primitive,
97
relativeTo,
108
realmInfo,
119
} from './card-api';
1210
import StringField from './string';
1311
import BooleanField from './boolean';
1412
import CodeRef from './code-ref';
15-
import {
16-
baseCardRef,
17-
isFieldDef,
18-
loadCard,
19-
Loader,
20-
} from '@cardstack/runtime-common';
21-
import { isEqual } from 'lodash';
13+
2214
import { FieldContainer } from '@cardstack/boxel-ui/components';
2315
import GlimmerComponent from '@glimmer/component';
2416

@@ -27,49 +19,10 @@ export class CatalogEntry extends CardDef {
2719
@field title = contains(StringField);
2820
@field description = contains(StringField);
2921
@field ref = contains(CodeRef);
30-
@field isPrimitive = contains(BooleanField, {
31-
computeVia: async function (this: CatalogEntry) {
32-
let loader = Loader.getLoaderFor(Object.getPrototypeOf(this).constructor);
33-
34-
if (!loader) {
35-
throw new Error(
36-
'Could not find a loader for this instance’s class’s module',
37-
);
38-
}
3922

40-
let card: typeof BaseDef | undefined = await loadCard(this.ref, {
41-
loader,
42-
relativeTo: this[relativeTo],
43-
});
44-
if (!card) {
45-
throw new Error(`Could not load card '${this.ref.name}'`);
46-
}
47-
// the base card is a special case where it is technically not a primitive, but because it has no fields
48-
// it is not useful to treat as a composite card (for the purposes of creating new card instances).
49-
return primitive in card || isEqual(baseCardRef, this.ref);
50-
},
51-
});
5223
// If it's not a field, then it's a card
53-
@field isField = contains(BooleanField, {
54-
computeVia: async function (this: CatalogEntry) {
55-
let loader = Loader.getLoaderFor(Object.getPrototypeOf(this).constructor);
24+
@field isField = contains(BooleanField);
5625

57-
if (!loader) {
58-
throw new Error(
59-
'Could not find a loader for this instance’s class’s module',
60-
);
61-
}
62-
63-
let card: typeof BaseDef | undefined = await loadCard(this.ref, {
64-
loader,
65-
relativeTo: this[relativeTo],
66-
});
67-
if (!card) {
68-
throw new Error(`Could not load card '${this.ref.name}'`);
69-
}
70-
return isFieldDef(card);
71-
},
72-
});
7326
@field moduleHref = contains(StringField, {
7427
computeVia: function (this: CatalogEntry) {
7528
return new URL(this.ref.module, this[relativeTo]).href;
@@ -164,10 +117,6 @@ export class CatalogEntry extends CardDef {
164117
{{#if @model.showDemo}}
165118
<div data-test-demo><@fields.demo /></div>
166119
{{/if}}
167-
{{! field needs to be used in order to be indexed }}
168-
{{#if @model.isPrimitive}}
169-
<div>Field is primitive</div>
170-
{{/if}}
171120
</CatalogEntryContainer>
172121
<style>
173122
.container {

packages/base/fields/base64-image.json

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"attributes": {
55
"title": "Base64 Image Field",
66
"description": "A field capable of representing an image via base64 encoding.",
7+
"isField": true,
78
"ref": {
89
"module": "https://cardstack.com/base/base64-image",
910
"name": "Base64ImageField"

packages/base/fields/biginteger-field.json

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"attributes": {
55
"title": "Bigint Field",
66
"description": "A field that captures big int values",
7+
"isField": true,
78
"ref": {
89
"module": "https://cardstack.com/base/big-integer",
910
"name": "default"

packages/base/fields/boolean-field.json

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"attributes": {
55
"title": "Boolean Field",
66
"description": "A field that captures boolean values (true/false)",
7+
"isField": true,
78
"ref": {
89
"module": "https://cardstack.com/base/boolean",
910
"name": "default"

packages/base/fields/code-ref-field.json

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"attributes": {
55
"title": "Code Reference Field",
66
"description": "A field that captures a reference to a card or field type",
7+
"isField": true,
78
"ref": {
89
"module": "https://cardstack.com/base/code-ref",
910
"name": "default"

packages/base/fields/date-field.json

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"attributes": {
55
"title": "Date Field",
66
"description": "A field that captures date values",
7+
"isField": true,
78
"ref": {
89
"module": "https://cardstack.com/base/date",
910
"name": "default"

packages/base/fields/datetime-field.json

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"attributes": {
55
"title": "Date-time Field",
66
"description": "A field that captures date-time values",
7+
"isField": true,
78
"ref": {
89
"module": "https://cardstack.com/base/datetime",
910
"name": "default"

packages/base/fields/ethereum-address-field.json

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"attributes": {
55
"title": "Ethereum Address Field",
66
"description": "A field that captures checksum ethereum addresses",
7+
"isField": true,
78
"ref": {
89
"module": "https://cardstack.com/base/ethereum-address",
910
"name": "default"

packages/base/fields/field.json

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"attributes": {
55
"title": "General Field",
66
"description": "A general field that can contain any field type.",
7+
"isField": true,
78
"ref": {
89
"module": "https://cardstack.com/base/card-api",
910
"name": "FieldDef"

packages/base/fields/markdown-field.json

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"attributes": {
55
"title": "Markdown Field",
66
"description": "A field that captures markdown values",
7+
"isField": true,
78
"ref": {
89
"module": "https://cardstack.com/base/markdown",
910
"name": "default"

0 commit comments

Comments
 (0)