Skip to content

Commit 8b4e305

Browse files
committed
Merge branch 'main' into cs-7993-support-multiple-tool-calls-per-ai-response
2 parents 8d2d4c6 + 0e42fb1 commit 8b4e305

40 files changed

+1185
-126
lines changed

packages/base/SkillCard/code-module-editing.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"data": {
33
"type": "card",
44
"attributes": {
5-
"instructions": "Boxel is a platform where people can create Cards, which under the hood are built out of glimmer components and ember.\n\nCards are independent linkable items that get an ID. Fields are contained within cards, so sometimes a user wants a custom field, but usually it's creating a card (derived from CardDef).\n\nUse glimmer templating and typescript for the code. Remember the limitations of logic within glimmer templating code. Basic interaction for editing fields is handled for you by boxel, you don't need to create that (e.g. StringField has an edit template that allows a user to edit the data). Computed fields can support more complex work, and update automatically for you. Interaction (button clicks, filtering on user typed content) may require glimmer & ember functionality (see action and tracked in the example below).\n\nCards you create have three templates. If you do not specify them they are automatically created for you, but users often want custom templates. Each template is a glimmer template and can use ember functionality. These are specified as static in the card definition:\n\nimport { contains, containsMany, linksToMany, field, CardDef, Component, } from 'https://cardstack.com/base/card-api'; import StringField from 'https://cardstack.com/base/string'; import NumberField from 'https://cardstack.com/base/number'; import BooleanField from 'https://cardstack.com/base/boolean'; // Important, this is the tracked decorator import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { fn } from '@ember/helper'; import { on } from '@ember/modifier';\n\nexport class MyCustomCard extends CardDef {\n\nstatic displayName = 'BoxelBuddyGuestList';\n\n// linksTo and linksToMany @field linkedData = linksToMany(() => AnotherCard);\n\n// A field that is computed from other data in the card @field computedData = contains(NumberField, { computeVia: function (this: MyCustomCard) { // implementation logic here return 1; }, });\n\n// Isolated templates are used when items are viewed on their own. Default to the isolated template static isolated = class Isolated extends Component { // Use tracked and action decorators to be able to use interactivity in the templates @tracked trackedValue = []; @action interactivity(event: InputEvent) {}\n\n// Glimmer template goes here, make sure the style tag is at the top level inside the template tag };\n\n// Embedded is when they appear in other cards static embedded = class Embedded extends Component { };\n\n// Fitted templates should be responsive to the size of the container they appear in static fitted = class Fitted extends Component { };\n\n// Edit is for the user editing the data. Use @fields let the field render itself static edit = class Edit extends Component { }; }\n\n@fields.fieldName lets the field render itself, very useful for editable fields. @model.fieldName gets the value out of the field.\n\nImportant:\n\nIt is extremely important you use the following imports for interactivity: import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { fn, get } from '@ember/helper'; import { on } from '@ember/modifier';\n\nRemember to define a field the following syntax is used:\n\n@field fieldname = contains(FieldType); @field fieldname = containsMany(FieldType);\n\nIf user asks you to make something editable, use contains or containsMany syntax for adding a field.\n\nAnd for linking to other cards:\n\n@field fieldname = linksTo(() => CardType); @field fieldname = linksToMany(() => CardType);\n\nYou can ask followups\n\nYou can propose new/improved data structures\n\nWhen writing the glimmer template, ensure that the style tags appear within the template tag, as the last item in them. You should use useful class names and a sensible structure as you build this. Use single quotes for the class names.\n\nWhen writing this, take care to remember ember and glimmer oddities. Accessing a list by index should use this format:\n\n{{(get this.args.model.fieldWithAList index)}}\n\nValues from the model can be directly inserted with\n\n{{this.args.model.fieldName}}\n\nand you can delegate rendering to the field with\n\n<@fields.fieldName />\n\nYou must be careful with the templates, remember glimmer rules. Do not put a dollar sign ($) directly in front of the brackets.\n\nAlways use scoped attributed when you generate a style tag, like so: <style scoped> ... CSS code ... </style>.\n\n<style> must be inside <template>, and the style tag must be the first child of <template>, not nested. So when you use <template>, the anatomy of should look like this:\n<template>\n... any html content...\n<style scoped>\n</style>\n</template>\n\nUnless otherwise instructed, use a modern but stylish theme. \n\nIn responses regarding to attached files, respond with a series of code patches where you output gts code and mark it whether it's for adding or deleting, in a clear succession, so that user can quickly just copy paste and put it in the code file, or delete code. \n\nUse multiple code snippets for every code change.",
5+
"instructions": "Boxel is a platform where people can create Cards, which under the hood are built out of glimmer components and ember.\n\nCards are independent linkable items that get an ID. Fields are contained within cards, so sometimes a user wants a custom field, but usually it's creating a card (derived from CardDef).\n\nUse glimmer templating and typescript for the code. Remember the limitations of logic within glimmer templating code. Basic interaction for editing fields is handled for you by boxel, you don't need to create that (e.g. StringField has an edit template that allows a user to edit the data). Computed fields can support more complex work, and update automatically for you. Interaction (button clicks, filtering on user typed content) may require glimmer & ember functionality (see action and tracked in the example below).\n\nCards you create have three templates. If you do not specify them they are automatically created for you, but users often want custom templates. Each template is a glimmer template and can use ember functionality. These are specified as static in the card definition:\n\nimport { contains, containsMany, linksToMany, field, CardDef, Component, } from 'https://cardstack.com/base/card-api'; import StringField from 'https://cardstack.com/base/string'; import NumberField from 'https://cardstack.com/base/number'; import BooleanField from 'https://cardstack.com/base/boolean'; // Important, this is the tracked decorator import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { fn } from '@ember/helper'; import { on } from '@ember/modifier';\n\nexport class MyCustomCard extends CardDef {\n\nstatic displayName = 'BoxelBuddyGuestList';\n\n// linksTo and linksToMany @field linkedData = linksToMany(() => AnotherCard);\n\n// A field that is computed from other data in the card @field computedData = contains(NumberField, { computeVia: function (this: MyCustomCard) { // implementation logic here return 1; }, });\n\n// Isolated templates are used when items are viewed on their own. Default to the isolated template static isolated = class Isolated extends Component { // Use tracked and action decorators to be able to use interactivity in the templates @tracked trackedValue = []; @action interactivity(event: InputEvent) {}\n\n// Glimmer template goes here, make sure the style tag is at the top level inside the template tag };\n\n// Embedded is when they appear in other cards static embedded = class Embedded extends Component { };\n\n// Fitted templates should be responsive to the size of the container they appear in static fitted = class Fitted extends Component { };\n\n// Edit is for the user editing the data. Use @fields let the field render itself static edit = class Edit extends Component { }; }\n\n@fields.fieldName lets the field render itself, very useful for editable fields. @model.fieldName gets the value out of the field.\n\nImportant:\n\nIt is extremely important you use the following imports for interactivity: import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { fn, get } from '@ember/helper'; import { on } from '@ember/modifier';\n\nRemember to define a field the following syntax is used:\n\n@field fieldname = contains(FieldType); @field fieldname = containsMany(FieldType);\n\nIf user asks you to make something editable, use contains or containsMany syntax for adding a field.\n\nAnd for linking to other cards:\n\n@field fieldname = linksTo(() => CardType); @field fieldname = linksToMany(() => CardType);\n\nYou can ask followups\n\nYou can propose new/improved data structures\n\nWhen writing the glimmer template, ensure that the style tags appear within the template tag, as the last item in them. You should use useful class names and a sensible structure as you build this. Use single quotes for the class names.\n\nWhen writing this, take care to remember ember and glimmer oddities. Accessing a list by index should use this format:\n\n{{(get this.args.model.fieldWithAList index)}}\n\nValues from the model can be directly inserted with\n\n{{this.args.model.fieldName}}\n\nand you can delegate rendering to the field with\n\n<@fields.fieldName />\n\nYou must be careful with the templates, remember glimmer rules. Do not put a dollar sign ($) directly in front of the brackets.\n\nAlways use scoped attributed when you generate a style tag, like so: <style scoped> ... CSS code ... </style>.\n\n<style> must be inside <template>, and the style tag must be the first child of <template>, not nested. So when you use <template>, the anatomy of should look like this:\n<template>\n... any html content...\n<style scoped>\n</style>\n</template>\n\nUnless otherwise instructed, use a modern but stylish theme. \n\nIn responses regarding to attached files, respond with a series of code patches where you output gts code and mark it whether it's for adding or deleting, in a clear succession, so that user can quickly just copy paste and put it in the code file, or delete code. \n\nUse multiple code snippets for every code change. Use gts language for markdown in your code snippets, like so: ```gts ...code... ```.",
66
"commands": [],
77
"title": "Code Module Editing",
88
"description": null,

packages/base/card-api.gts

+2-2
Original file line numberDiff line numberDiff line change
@@ -350,12 +350,12 @@ function callSerializeHook(
350350

351351
function cardTypeFor(
352352
field: Field<typeof BaseDef>,
353-
boxedElement: Box<BaseDef>,
353+
boxedElement?: Box<BaseDef>,
354354
): typeof BaseDef {
355355
if (primitive in field.card) {
356356
return field.card;
357357
}
358-
if (boxedElement.value == null) {
358+
if (boxedElement === undefined || boxedElement.value == null) {
359359
return field.card;
360360
}
361361
return Reflect.getPrototypeOf(boxedElement.value)!

packages/base/field-component.gts

+14-18
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,11 @@ export class PermissionsConsumer extends Component<PermissionsConsumerSignature>
118118

119119
const componentCache = initSharedState(
120120
'componentCache',
121-
() => new WeakMap<Box<BaseDef>, BoxComponent>(),
121+
() =>
122+
new WeakMap<
123+
Box<BaseDef>,
124+
{ component: BoxComponent; cardOrField: typeof BaseDef }
125+
>(),
122126
);
123127

124128
export function getBoxComponent(
@@ -130,8 +134,8 @@ export function getBoxComponent(
130134
// the componentCodeRef is only set on the server during card prerendering,
131135
// it should have no effect on component stability
132136
let stable = componentCache.get(model);
133-
if (stable) {
134-
return stable;
137+
if (stable?.cardOrField === cardOrField) {
138+
return stable.component;
135139
}
136140
function determineFormats(
137141
userFormat: Format | undefined,
@@ -364,9 +368,13 @@ export function getBoxComponent(
364368
let externalFields = fieldsComponentsFor(component, model);
365369

366370
// This cast is safe because we're returning a proxy that wraps component.
367-
stable = externalFields as unknown as typeof component;
371+
stable = {
372+
component: externalFields as unknown as typeof component,
373+
cardOrField: cardOrField,
374+
};
375+
368376
componentCache.set(model, stable);
369-
return stable;
377+
return stable.component;
370378
}
371379

372380
function defaultFieldFormats(containingFormat: Format): FieldFormats {
@@ -386,10 +394,6 @@ function fieldsComponentsFor<T extends BaseDef>(
386394
target: object,
387395
model: Box<T>,
388396
): FieldsTypeFor<T> {
389-
// This is a cache of the fields we've already created components for
390-
// so that they do not get recreated
391-
let stableComponents = new Map<string, BoxComponent>();
392-
393397
return new Proxy(target, {
394398
get(target, property, received) {
395399
if (
@@ -401,11 +405,6 @@ function fieldsComponentsFor<T extends BaseDef>(
401405
return Reflect.get(target, property, received);
402406
}
403407

404-
let stable = stableComponents.get(property);
405-
if (stable) {
406-
return stable;
407-
}
408-
409408
let modelValue = model.value as T; // TS is not picking up the fact we already filtered out nulls and undefined above
410409
let maybeField: Field<BaseDefConstructor> | undefined = getField(
411410
modelValue.constructor,
@@ -416,10 +415,7 @@ function fieldsComponentsFor<T extends BaseDef>(
416415
return Reflect.get(target, property, received);
417416
}
418417
let field = maybeField;
419-
420-
let result = field.component(model as unknown as Box<BaseDef>);
421-
stableComponents.set(property, result);
422-
return result;
418+
return field.component(model as unknown as Box<BaseDef>);
423419
},
424420
getPrototypeOf() {
425421
// This is necessary for Ember to be able to locate the template associated

packages/base/links-to-many-component.gts

+17-2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {
4343
} from '@cardstack/boxel-ui/modifiers';
4444

4545
import { action } from '@ember/object';
46+
import { initSharedState } from './shared-state';
4647

4748
interface Signature {
4849
Element: HTMLElement;
@@ -151,7 +152,7 @@ class LinksToManyStandardEditor extends GlimmerComponent<LinksToManyStandardEdit
151152

152153
@action
153154
setItems(items: any) {
154-
(this.args.model.value as any)[this.args.field.name] = items;
155+
this.args.arrayField.set(items);
155156
}
156157

157158
<template>
@@ -380,6 +381,10 @@ function shouldRenderEditor(
380381
) {
381382
return (format ?? defaultFormat) === 'edit' && !isComputed;
382383
}
384+
const componentCache = initSharedState(
385+
'linksToManyComponentCache',
386+
() => new WeakMap<Box<BaseDef[]>, { component: BoxComponent }>(),
387+
);
383388

384389
export function getLinksToManyComponent({
385390
model,
@@ -395,6 +400,10 @@ export function getLinksToManyComponent({
395400
boxedElement: Box<BaseDef>,
396401
): typeof BaseDef;
397402
}): BoxComponent {
403+
let stable = componentCache.get(arrayField);
404+
if (stable) {
405+
return stable.component;
406+
}
398407
let getComponents = () =>
399408
arrayField.children.map((child) =>
400409
getBoxComponent(cardTypeFor(field, child), child, field),
@@ -465,7 +474,7 @@ export function getLinksToManyComponent({
465474
</style>
466475
</template>
467476
};
468-
return new Proxy(linksToManyComponent, {
477+
let proxy = new Proxy(linksToManyComponent, {
469478
get(target, property, received) {
470479
// proxying the bare minimum of an Array in order to render within a
471480
// template. add more getters as necessary...
@@ -490,6 +499,12 @@ export function getLinksToManyComponent({
490499
return linksToManyComponent;
491500
},
492501
});
502+
stable = {
503+
component: proxy as unknown as BoxComponent,
504+
};
505+
506+
componentCache.set(arrayField, stable);
507+
return stable.component;
493508
}
494509

495510
function myLoader(): Loader {

0 commit comments

Comments
 (0)