diff --git a/QUICKSTART.md b/QUICKSTART.md index faf6f794ca..48b7bad8c1 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -107,7 +107,5 @@ If you experience issues, you can start from scratch by running this command ``` pnpm clear-caches -rm -rf ./packages/matrix/synapse-data -docker ps -a --format '{{.Names}}' | grep -E 'boxel-smtp|boxel-synapse|synapse-admin' | xargs -r docker stop -docker ps -a --format '{{.Names}}' | grep -E 'boxel-smtp|boxel-synapse|synapse-admin' | xargs -r docker rm -v +pnpm full-reset ``` diff --git a/packages/ai-bot/helpers.ts b/packages/ai-bot/helpers.ts index 00d122cdad..6d6ad4dc9f 100644 --- a/packages/ai-bot/helpers.ts +++ b/packages/ai-bot/helpers.ts @@ -8,20 +8,22 @@ import type { MatrixEvent as DiscreteMatrixEvent, CardFragmentContent, CommandEvent, - CommandResultEvent, - ReactionEvent, Tool, SkillsConfigEvent, + CommandResultEvent, } from 'https://cardstack.com/base/matrix-event'; import { MatrixEvent, type IRoomEvent } from 'matrix-js-sdk'; import { ChatCompletionMessageToolCall } from 'openai/resources/chat/completions'; import * as Sentry from '@sentry/node'; import { logger } from '@cardstack/runtime-common'; +import { + APP_BOXEL_COMMAND_RESULT_EVENT_TYPE, + APP_BOXEL_COMMAND_RESULT_WITH_OUTPUT_MSGTYPE, +} from '../runtime-common/matrix-constants'; import { APP_BOXEL_CARDFRAGMENT_MSGTYPE, APP_BOXEL_MESSAGE_MSGTYPE, APP_BOXEL_COMMAND_MSGTYPE, - APP_BOXEL_COMMAND_RESULT_MSGTYPE, APP_BOXEL_ROOM_SKILLS_EVENT_TYPE, } from '@cardstack/runtime-common/matrix-constants'; @@ -140,6 +142,16 @@ export function constructHistory( } } let event = { ...rawEvent } as DiscreteMatrixEvent; + if ( + event.type === APP_BOXEL_COMMAND_RESULT_EVENT_TYPE && + event.content.msgtype == APP_BOXEL_COMMAND_RESULT_WITH_OUTPUT_MSGTYPE + ) { + let { cardEventId } = event.content.data; + event.content.data.card = serializedCardFromFragments( + cardEventId, + cardFragments, + ); + } if (event.type !== 'm.room.message') { continue; } @@ -358,48 +370,11 @@ export function getToolChoice( return 'auto'; } -export function isCommandResultEvent( - event: DiscreteMatrixEvent, -): event is CommandResultEvent { - return ( - event.type === 'm.room.message' && - typeof event.content === 'object' && - event.content.msgtype === APP_BOXEL_COMMAND_RESULT_MSGTYPE - ); -} - -export function isReactionEvent( - event: DiscreteMatrixEvent, -): event is ReactionEvent { - return ( - event.type === 'm.reaction' && - event.content['m.relates_to'].rel_type === 'm.annotation' - ); -} - -function getReactionStatus( - commandEvent: DiscreteMatrixEvent, - history: DiscreteMatrixEvent[], -) { - let maybeReactionEvent = history.find((e) => { - if ( - isReactionEvent(e) && - e.content['m.relates_to']?.event_id === commandEvent.event_id - ) { - return true; - } - return false; - }); - return maybeReactionEvent && isReactionEvent(maybeReactionEvent) - ? maybeReactionEvent.content['m.relates_to'].key - : undefined; -} - function getCommandResult( commandEvent: CommandEvent, history: DiscreteMatrixEvent[], ) { - let maybeCommandResultEvent = history.find((e) => { + let commandResultEvent = history.find((e) => { if ( isCommandResultEvent(e) && e.content['m.relates_to']?.event_id === commandEvent.event_id @@ -407,11 +382,8 @@ function getCommandResult( return true; } return false; - }); - return maybeCommandResultEvent && - isCommandResultEvent(maybeCommandResultEvent) - ? maybeCommandResultEvent.content.result - : undefined; + }) as CommandResultEvent | undefined; + return commandResultEvent; } function toToolCall(event: CommandEvent): ChatCompletionMessageToolCall { @@ -429,21 +401,26 @@ function toPromptMessageWithToolResult( event: CommandEvent, history: DiscreteMatrixEvent[], ): OpenAIPromptMessage { - let commandResult = getCommandResult(event as CommandEvent, history); + let commandResult = getCommandResult(event, history); + let content = 'pending'; if (commandResult) { - return { - role: 'tool', - content: commandResult, - tool_call_id: event.content.data.toolCall.id, - }; - } else { - let reactionStatus = getReactionStatus(event, history); - return { - role: 'tool', - content: reactionStatus ?? 'pending', - tool_call_id: event.content.data.toolCall.id, - }; + let status = commandResult.content['m.relates_to']?.key; + if ( + commandResult.content.msgtype === + APP_BOXEL_COMMAND_RESULT_WITH_OUTPUT_MSGTYPE + ) { + content = `Command ${status}, with result card: ${JSON.stringify( + commandResult.content.data.card, + )}.\n`; + } else { + content = `Command ${status}.\n`; + } } + return { + role: 'tool', + content, + tool_call_id: event.content.data.toolCall.id, + }; } export function getModifyPrompt( @@ -570,24 +547,13 @@ export function cleanContent(content: string) { return content.trim(); } -export const isCommandReactionEvent = (event?: MatrixEvent) => { - if (event === undefined) { - return false; - } - let content = event.getContent(); - return ( - event.getType() === 'm.reaction' && - content['m.relates_to']?.rel_type === 'm.annotation' - ); -}; - -export const isCommandReactionStatusApplied = (event?: MatrixEvent) => { +export const isCommandResultStatusApplied = (event?: MatrixEvent) => { if (event === undefined) { return false; } - let content = event.getContent(); return ( - isCommandReactionEvent(event) && content['m.relates_to']?.key === 'applied' + isCommandResultEvent(event.event as DiscreteMatrixEvent) && + event.getContent()['m.relates_to']?.key === 'applied' ); }; @@ -603,3 +569,15 @@ export function isCommandEvent( typeof event.content.data.toolCall === 'object' ); } + +export function isCommandResultEvent( + event?: DiscreteMatrixEvent, +): event is CommandResultEvent { + if (event === undefined) { + return false; + } + return ( + event.type === APP_BOXEL_COMMAND_RESULT_EVENT_TYPE && + event.content['m.relates_to']?.rel_type === 'm.annotation' + ); +} diff --git a/packages/ai-bot/lib/set-title.ts b/packages/ai-bot/lib/set-title.ts index e207a80f2d..61dac91c8d 100644 --- a/packages/ai-bot/lib/set-title.ts +++ b/packages/ai-bot/lib/set-title.ts @@ -1,18 +1,23 @@ -import { type MatrixEvent, type IEventRelation } from 'matrix-js-sdk'; +import { + type MatrixEvent, + type IEventRelation, + IRoomEvent, +} from 'matrix-js-sdk'; import OpenAI from 'openai'; import { type OpenAIPromptMessage, - isCommandReactionStatusApplied, + isCommandResultStatusApplied, attachedCardsToMessage, isCommandEvent, getRelevantCards, } from '../helpers'; import { MatrixClient } from './matrix'; import type { MatrixEvent as DiscreteMatrixEvent } from 'https://cardstack.com/base/matrix-event'; +import { ChatCompletionMessageParam } from 'openai/resources'; const SET_TITLE_SYSTEM_MESSAGE = `You are a chat titling system, you must read the conversation and return a suggested title of no more than six words. -Do NOT say talk or discussion or discussing or chat or chatting, this is implied by the context. -The user can optionally apply 'patchCard' by sending data about fields to update. +Do NOT say talk or discussion or discussing or chat or chatting, this is implied by the context. +The user can optionally apply 'patchCard' by sending data about fields to update. Explain the general actions and user intent. If 'patchCard' was used, express the title in an active sentence. Do NOT use the word "patch" in the title.`; export async function setTitle( @@ -39,7 +44,7 @@ export async function setTitle( let result = await openai.chat.completions.create( { model: 'gpt-4o', - messages: startOfConversation, + messages: startOfConversation as ChatCompletionMessageParam[], stream: false, }, { @@ -120,7 +125,7 @@ export const getLatestCommandApplyMessage = ( return []; }; -export const roomTitleAlreadySet = (rawEventLog: DiscreteMatrixEvent[]) => { +export const roomTitleAlreadySet = (rawEventLog: IRoomEvent[]) => { return ( rawEventLog.filter((event) => event.type === 'm.room.name').length > 1 ?? false @@ -128,7 +133,7 @@ export const roomTitleAlreadySet = (rawEventLog: DiscreteMatrixEvent[]) => { }; const userAlreadyHasSentNMessages = ( - rawEventLog: DiscreteMatrixEvent[], + rawEventLog: IRoomEvent[], botUserId: string, n = 5, ) => { @@ -140,12 +145,12 @@ const userAlreadyHasSentNMessages = ( }; export function shouldSetRoomTitle( - rawEventLog: DiscreteMatrixEvent[], + rawEventLog: IRoomEvent[], aiBotUserId: string, event?: MatrixEvent, ) { return ( - (isCommandReactionStatusApplied(event) || + (isCommandResultStatusApplied(event) || userAlreadyHasSentNMessages(rawEventLog, aiBotUserId)) && !roomTitleAlreadySet(rawEventLog) ); diff --git a/packages/ai-bot/main.ts b/packages/ai-bot/main.ts index 82dc5a5877..447e64d37e 100644 --- a/packages/ai-bot/main.ts +++ b/packages/ai-bot/main.ts @@ -11,7 +11,7 @@ import { logger, aiBotUsername } from '@cardstack/runtime-common'; import { type PromptParts, constructHistory, - isCommandReactionStatusApplied, + isCommandResultStatusApplied, getPromptParts, extractCardFragmentsFromEvents, } from './helpers'; @@ -30,6 +30,8 @@ import * as Sentry from '@sentry/node'; import { getAvailableCredits, saveUsageCost } from './lib/ai-billing'; import { PgAdapter } from '@cardstack/postgres'; +import { ChatCompletionMessageParam } from 'openai/resources'; +import { OpenAIError } from 'openai/error'; let log = logger('ai-bot'); @@ -69,12 +71,12 @@ class Assistant { if (prompt.tools.length === 0) { return this.openai.beta.chat.completions.stream({ model: prompt.model, - messages: prompt.messages, + messages: prompt.messages as ChatCompletionMessageParam[], }); } else { return this.openai.beta.chat.completions.stream({ model: prompt.model, - messages: prompt.messages, + messages: prompt.messages as ChatCompletionMessageParam[], tools: prompt.tools, tool_choice: prompt.toolChoice, }); @@ -250,7 +252,7 @@ Common issues are: finalContent = await runner.finalContent(); await responder.finalize(finalContent); } catch (error) { - await responder.onError(error); + await responder.onError(error as OpenAIError); } finally { if (generationId) { assistant.trackAiUsageCost(senderMatrixUserId, generationId); @@ -278,7 +280,7 @@ Common issues are: if (!room) { return; } - if (!isCommandReactionStatusApplied(event)) { + if (!isCommandResultStatusApplied(event)) { return; } log.info( diff --git a/packages/ai-bot/package.json b/packages/ai-bot/package.json index 6f7adb5927..38ac39f742 100644 --- a/packages/ai-bot/package.json +++ b/packages/ai-bot/package.json @@ -18,6 +18,7 @@ }, "devDependencies": { "@sinonjs/fake-timers": "^11.2.2", + "@types/qunit": "^2.19.12", "@types/sinonjs__fake-timers": "^8.1.5", "qunit": "^2.18.0" }, diff --git a/packages/ai-bot/tests/chat-titling-test.ts b/packages/ai-bot/tests/chat-titling-test.ts index f395c62a6f..393f0d56e4 100644 --- a/packages/ai-bot/tests/chat-titling-test.ts +++ b/packages/ai-bot/tests/chat-titling-test.ts @@ -1,7 +1,12 @@ import { module, test, assert } from 'qunit'; import { shouldSetRoomTitle } from '../lib/set-title'; import type { MatrixEvent as DiscreteMatrixEvent } from 'https://cardstack.com/base/matrix-event'; -import { APP_BOXEL_COMMAND_MSGTYPE } from '@cardstack/runtime-common/matrix-constants'; +import { + APP_BOXEL_COMMAND_MSGTYPE, + APP_BOXEL_COMMAND_RESULT_EVENT_TYPE, + APP_BOXEL_COMMAND_RESULT_WITH_NO_OUTPUT_MSGTYPE, +} from '@cardstack/runtime-common/matrix-constants'; +import { IEvent, IRoomEvent, MatrixEvent } from 'matrix-js-sdk'; module('shouldSetRoomTitle', () => { test('Do not set a title when there is no content', () => { @@ -10,7 +15,7 @@ module('shouldSetRoomTitle', () => { }); test('Do not set a title when there is little content', () => { - const eventLog: DiscreteMatrixEvent[] = [ + const eventLog: IRoomEvent[] = [ { type: 'm.room.message', event_id: '1', @@ -33,7 +38,7 @@ module('shouldSetRoomTitle', () => { }); test('Do not set a title when there are more than 5 messages but they are state/invites/etc', () => { - const eventLog: DiscreteMatrixEvent[] = [ + const eventLog: IRoomEvent[] = [ { type: 'm.room.message', event_id: '1', @@ -78,7 +83,7 @@ module('shouldSetRoomTitle', () => { }, sender: '@user:localhost', room_id: 'room1', - state_key: 'a', + state_key: '', unsigned: { age: 1000, }, @@ -90,7 +95,7 @@ module('shouldSetRoomTitle', () => { content: {}, sender: '@user:localhost', room_id: 'room1', - state_key: 'b', + state_key: '', unsigned: { age: 1000, }, @@ -105,7 +110,7 @@ module('shouldSetRoomTitle', () => { }, sender: '@user:localhost', room_id: 'room1', - state_key: 'c', + state_key: '', unsigned: { age: 1000, }, @@ -120,7 +125,7 @@ module('shouldSetRoomTitle', () => { }, sender: '@user:localhost', room_id: 'room1', - state_key: 'd', + state_key: '', unsigned: { age: 1000, }, @@ -130,7 +135,7 @@ module('shouldSetRoomTitle', () => { }); test('Do not set a title when there are under 5 user messages but more than 5 total messages', () => { - const eventLog: DiscreteMatrixEvent[] = [ + const eventLog: IRoomEvent[] = [ { type: 'm.room.message', event_id: '1', @@ -239,7 +244,7 @@ module('shouldSetRoomTitle', () => { }); test('Set a title when there are 5 or more user messages', () => { - const eventLog: DiscreteMatrixEvent[] = [ + const eventLog: IRoomEvent[] = [ { type: 'm.room.message', event_id: '1', @@ -348,7 +353,7 @@ module('shouldSetRoomTitle', () => { }); test('Title is not set if the bot has sent ONLY a command', () => { - const eventLog: DiscreteMatrixEvent[] = [ + const eventLog: IRoomEvent[] = [ { type: 'm.room.message', event_id: '1', @@ -403,21 +408,18 @@ module('shouldSetRoomTitle', () => { }); test('Set a title if the user applied a command', () => { - let patchReactionEvent = { - getContent() { - return { - 'm.relates_to': { - event_id: '1', - key: 'applied', - rel_type: 'm.annotation', - }, - }; - }, - getType() { - return 'm.reaction'; + let patchCommandResultEvent: Partial = { + type: APP_BOXEL_COMMAND_RESULT_EVENT_TYPE, + content: { + 'm.relates_to': { + event_id: '1', + key: 'applied', + rel_type: 'm.annotation', + }, + msgtype: APP_BOXEL_COMMAND_RESULT_WITH_NO_OUTPUT_MSGTYPE, }, }; - const eventLog: DiscreteMatrixEvent[] = [ + const eventLog: IRoomEvent[] = [ { type: 'm.room.message', event_id: '1', @@ -469,7 +471,11 @@ module('shouldSetRoomTitle', () => { }, ]; assert.true( - shouldSetRoomTitle(eventLog, '@aibot:localhost', patchReactionEvent), + shouldSetRoomTitle( + eventLog, + '@aibot:localhost', + new MatrixEvent(patchCommandResultEvent), + ), ); }); }); diff --git a/packages/ai-bot/tests/history-construction-test.ts b/packages/ai-bot/tests/history-construction-test.ts index 1694a49a9d..afb7a10344 100644 --- a/packages/ai-bot/tests/history-construction-test.ts +++ b/packages/ai-bot/tests/history-construction-test.ts @@ -10,7 +10,7 @@ import { APP_BOXEL_MESSAGE_MSGTYPE, } from '@cardstack/runtime-common/matrix-constants'; -import { type IRoomEvent } from 'matrix-js-sdk'; +import { EventStatus, type IRoomEvent } from 'matrix-js-sdk'; import type { MatrixEvent as DiscreteMatrixEvent } from 'https://cardstack.com/base/matrix-event'; module('constructHistory', () => { @@ -38,6 +38,7 @@ module('constructHistory', () => { unsigned: { age: 1000, }, + status: EventStatus.SENT, }, { type: 'm.room.join_rules', @@ -50,6 +51,7 @@ module('constructHistory', () => { unsigned: { age: 1001, }, + status: EventStatus.SENT, }, { type: 'm.room.member', @@ -65,6 +67,7 @@ module('constructHistory', () => { unsigned: { age: 1002, }, + status: EventStatus.SENT, }, ]; @@ -74,7 +77,7 @@ module('constructHistory', () => { }); test('should return an array with a single message event when the input array contains only one message event', () => { - const eventlist: DiscreteMatrixEvent[] = [ + const eventlist: IRoomEvent[] = [ { type: 'm.room.message', event_id: '1', @@ -100,7 +103,7 @@ module('constructHistory', () => { }); test('should return an array with all message events when the input array contains multiple message events', () => { - const history: DiscreteMatrixEvent[] = [ + const history: IRoomEvent[] = [ { type: 'm.room.message', event_id: '1', @@ -160,7 +163,7 @@ module('constructHistory', () => { }); test('should return an array with all message events when the input array contains multiple events with the same origin_server_ts', () => { - const history: DiscreteMatrixEvent[] = [ + const history: IRoomEvent[] = [ { type: 'm.room.message', event_id: '1', @@ -220,7 +223,7 @@ module('constructHistory', () => { }); test('should return an array of DiscreteMatrixEvent objects with no duplicates based on event_id even when m.relates_to is present and include senders and origin_server_ts', () => { - const history: DiscreteMatrixEvent[] = [ + const history: IRoomEvent[] = [ // this event will _not_ replace event_id 2 since it's timestamp is before event_id 2 { event_id: '1', diff --git a/packages/ai-bot/tests/prompt-construction-test.ts b/packages/ai-bot/tests/prompt-construction-test.ts index 5acda2222d..14621bd5ec 100644 --- a/packages/ai-bot/tests/prompt-construction-test.ts +++ b/packages/ai-bot/tests/prompt-construction-test.ts @@ -13,8 +13,9 @@ import { } from '../helpers'; import { APP_BOXEL_MESSAGE_MSGTYPE, - APP_BOXEL_COMMAND_RESULT_MSGTYPE, APP_BOXEL_COMMAND_MSGTYPE, + APP_BOXEL_COMMAND_RESULT_EVENT_TYPE, + APP_BOXEL_COMMAND_RESULT_WITH_OUTPUT_MSGTYPE, } from '@cardstack/runtime-common/matrix-constants'; import type { @@ -1309,7 +1310,6 @@ test('Return host result of tool call back to open ai', () => { age: 20470, }, event_id: '$p_NQ4tvokzQrIkT24Wj08mdAxBBvmdLOz6ph7UQfMDw', - user_id: '@tintinthong:localhost', age: 20470, }, { @@ -1341,7 +1341,6 @@ test('Return host result of tool call back to open ai', () => { transaction_id: 'm1722242836705.8', }, event_id: 'message-event-id-1', - user_id: '@aibot:localhost', age: 17305, }, { @@ -1458,7 +1457,6 @@ test('Return host result of tool call back to open ai', () => { age: 6614, }, event_id: '$FO2XfB0xFiTpm5FmOUiWQqFh_DPQSr4zix41Vj3eqNc', - user_id: '@tintinthong:localhost', age: 6614, }, { @@ -1500,11 +1498,10 @@ test('Return host result of tool call back to open ai', () => { transaction_id: 'm1722242849075.10', }, event_id: 'command-event-id-1', - user_id: '@ai-bot:localhost', age: 4938, }, { - type: 'm.room.message', + type: APP_BOXEL_COMMAND_RESULT_EVENT_TYPE, room_id: 'room-id-1', sender: '@tintinthong:localhost', content: { @@ -1513,19 +1510,43 @@ test('Return host result of tool call back to open ai', () => { rel_type: 'm.annotation', key: 'applied', }, - body: 'Command Results from command event $H7dH0ZzG0W3M_1k_YRjnDOirWRthYvWq7TKmfAfhQqw', - formatted_body: - '

Command Results from command event $H7dH0ZzG0W3M_1k_YRjnDOirWRthYvWq7TKmfAfhQqw

\n', - msgtype: APP_BOXEL_COMMAND_RESULT_MSGTYPE, - result: - '[{"data":{"type":"card","id":"http://localhost:4201/drafts/Author/1","attributes":{"firstName":"Alice","lastName":"Enwunder","photo":null,"body":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.","description":null,"thumbnailURL":null},"meta":{"adoptsFrom":{"module":"../author","name":"Author"}}}}]', + msgtype: APP_BOXEL_COMMAND_RESULT_WITH_OUTPUT_MSGTYPE, + data: { + card: JSON.stringify({ + data: { + type: 'card', + attributes: { + title: 'Search Results', + description: 'Here are the search results', + results: [ + { + data: { + type: 'card', + id: 'http://localhost:4201/drafts/Author/1', + attributes: { + firstName: 'Alice', + lastName: 'Enwunder', + photo: null, + body: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.', + description: null, + thumbnailURL: null, + }, + meta: { + adoptsFrom: { module: '../author', name: 'Author' }, + }, + }, + }, + ], + }, + }, + }), + }, }, origin_server_ts: 1722242853988, unsigned: { age: 44, }, event_id: 'command-result-id-1', - user_id: '@tintinthong:localhost', age: 44, }, ]; @@ -1533,10 +1554,8 @@ test('Return host result of tool call back to open ai', () => { const result = getModifyPrompt(history, '@ai-bot:localhost', tools); assert.equal(result[5].role, 'tool'); assert.equal(result[5].tool_call_id, 'tool-call-id-1'); - assert.equal( - result[5].content, - '[{"data":{"type":"card","id":"http://localhost:4201/drafts/Author/1","attributes":{"firstName":"Alice","lastName":"Enwunder","photo":null,"body":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.","description":null,"thumbnailURL":null},"meta":{"adoptsFrom":{"module":"../author","name":"Author"}}}}]', - ); + const expected = `Command applied, with result card: "{\\"data\\":{\\"type\\":\\"card\\",\\"attributes\\":{\\"title\\":\\"Search Results\\",\\"description\\":\\"Here are the search results\\",\\"results\\":[{\\"data\\":{\\"type\\":\\"card\\",\\"id\\":\\"http://localhost:4201/drafts/Author/1\\",\\"attributes\\":{\\"firstName\\":\\"Alice\\",\\"lastName\\":\\"Enwunder\\",\\"photo\\":null,\\"body\\":\\"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.\\",\\"description\\":null,\\"thumbnailURL\\":null},\\"meta\\":{\\"adoptsFrom\\":{\\"module\\":\\"../author\\",\\"name\\":\\"Author\\"}}}}]}}}".\n`; + assert.equal(result[5].content, expected); }); test('Tools remain available in prompt parts even when not in last message', () => { diff --git a/packages/base/card-api.gts b/packages/base/card-api.gts index 9295153dc1..9ada4e7726 100644 --- a/packages/base/card-api.gts +++ b/packages/base/card-api.gts @@ -1297,17 +1297,22 @@ class LinksToMany cachedInstance[isSavedInstance] = true; return cachedInstance; } - let resourceId = new URL(value.links.self, relativeTo).href; + let resourceId = new URL( + value.links.self, + !Array.isArray(doc.data) && 'id' in doc.data && doc.data.id + ? doc.data.id + : relativeTo, + ).href; + if (loadedValues && Array.isArray(loadedValues)) { + let loadedValue = loadedValues.find( + (v) => isCardOrField(v) && 'id' in v && v.id === resourceId, + ); + if (loadedValue) { + return loadedValue; + } + } let resource = resourceFrom(doc, resourceId); if (!resource) { - if (loadedValues && Array.isArray(loadedValues)) { - let loadedValue = loadedValues.find( - (v) => isCardOrField(v) && 'id' in v && v.id === resourceId, - ); - if (loadedValue) { - return loadedValue; - } - } return { type: 'not-loaded', reference: value.links.self, diff --git a/packages/base/command.gts b/packages/base/command.gts index 4972816c9e..8254fc996c 100644 --- a/packages/base/command.gts +++ b/packages/base/command.gts @@ -1,56 +1,34 @@ import { CardDef, Component, - FieldDef, StringField, contains, containsMany, field, linksTo, linksToMany, - primitive, - queryableValue, } from './card-api'; import CodeRefField from './code-ref'; import BooleanField from './boolean'; +import NumberField from './number'; import { SkillCard } from './skill-card'; +import { JsonField, SearchCardsResult } from './commands/search-card-result'; export type CommandStatus = 'applied' | 'ready' | 'applying'; -class CommandObjectFieldTemplate extends Component { - - - get stringValue() { - return JSON.stringify(this.args.model, null, 2); - } -} - -export class CommandObjectField extends FieldDef { - static [primitive]: Record; - static [queryableValue](value: Record | undefined) { - return Boolean(value) && typeof value === 'object' - ? JSON.stringify(value) - : undefined; - } - static edit = CommandObjectFieldTemplate; - static embedded = CommandObjectFieldTemplate; -} - export class SaveCardInput extends CardDef { @field realm = contains(StringField); @field card = linksTo(CardDef); } -class JsonField extends FieldDef { - static [primitive]: Record; +export class CopyCardInput extends CardDef { + @field sourceCard = linksTo(CardDef); + @field targetRealmUrl = contains(StringField); + @field targetStackIndex = contains(NumberField); +} + +export class CopyCardResult extends CardDef { + @field newCard = linksTo(CardDef); } export class PatchCardInput extends CardDef { @@ -114,6 +92,30 @@ export class SendAiAssistantMessageResult extends CardDef { @field eventId = contains(StringField); } +export class GetBoxelUIStateResult extends CardDef { + @field submode = contains(StringField); + //TODO expand this to include more of the UI state: + // - open cards + // - current room ID + static embedded = class Embedded extends Component< + typeof GetBoxelUIStateResult + > { + + }; +} + +export class LegacyGenerateAppModuleResult extends CardDef { + @field moduleId = contains(StringField); + @field source = contains(StringField); +} + export class OpenAiAssistantRoomInput extends CardDef { @field roomId = contains(StringField); } + +export { SearchCardsResult }; diff --git a/packages/base/command-result.gts b/packages/base/commands/search-card-result.gts similarity index 58% rename from packages/base/command-result.gts rename to packages/base/commands/search-card-result.gts index 4bb10ffbde..2a41f4ab12 100644 --- a/packages/base/command-result.gts +++ b/packages/base/commands/search-card-result.gts @@ -1,25 +1,15 @@ import GlimmerComponent from '@glimmer/component'; import { cached, tracked } from '@glimmer/tracking'; -import { array } from '@ember/helper'; import { on } from '@ember/modifier'; import { action } from '@ember/object'; +import { Button, FieldContainer } from '@cardstack/boxel-ui/components'; +import { eq } from '@cardstack/boxel-ui/helpers'; import { - BoxelDropdown, - Button, - FieldContainer, - Header, - IconButton, - Menu, -} from '@cardstack/boxel-ui/components'; -import { eq, menuItem } from '@cardstack/boxel-ui/helpers'; -import { - ArrowLeft, IconMinusCircle, IconPlus, IconSearchThick, - ThreeDotsHorizontal, } from '@cardstack/boxel-ui/icons'; -import { getCard } from '@cardstack/runtime-common'; +import { getCard, primitive } from '@cardstack/runtime-common'; import { BaseDef, CardDef, @@ -30,8 +20,8 @@ import { field, type CardContext, type Format, -} from './card-api'; -import { CommandObjectField } from './command'; + FieldDef, +} from '../card-api'; type AttachedCardResource = { card: CardDef | undefined; @@ -48,6 +38,10 @@ interface ResourceListSignature { context?: CardContext; } +export class JsonField extends FieldDef { + static [primitive]: Record; +} + class ResourceList extends GlimmerComponent { } -class CommandResultEmbeddedView extends Component { +class SearchCardsResultEmbeddedView extends Component< + typeof SearchCardsResult +> { @tracked showAllResults = false; @cached @@ -181,74 +177,25 @@ class CommandResultEmbeddedView extends Component { } - - @action async copyToWorkspace() { - let newCard = await this.args.context?.actions?.copyCard?.( - this.args.model as CardDef, - ); - if (!newCard) { - console.error('Could not copy card to workspace.'); - return; - } - this.args.context?.actions?.viewCard(newCard, 'isolated', { - openCardInRightMostStack: true, - }); - } } -class CommandResultIsolated extends CommandResultEmbeddedView { +class SearchCardsResultIsolatedView extends SearchCardsResultEmbeddedView { ; diff --git a/packages/base/default-templates/atom.gts b/packages/base/default-templates/atom.gts index 823a8d5cd5..282f14760a 100644 --- a/packages/base/default-templates/atom.gts +++ b/packages/base/default-templates/atom.gts @@ -18,6 +18,16 @@ export default class DefaultAtomViewTemplate extends GlimmerComponent<{ : `Untitled ${this.args.model.constructor.displayName}`; } } diff --git a/packages/base/default-templates/isolated-and-edit.gts b/packages/base/default-templates/isolated-and-edit.gts index 06e2e2b34f..91f1dbf9ab 100644 --- a/packages/base/default-templates/isolated-and-edit.gts +++ b/packages/base/default-templates/isolated-and-edit.gts @@ -43,14 +43,19 @@ export default class DefaultCardDefTemplate extends GlimmerComponent<{ } /* this aligns edit fields with containsMany, linksTo, and linksToMany fields */ .default-card-template.edit - > .boxel-field > :deep( - *:nth-child(2):not(.links-to-many-editor):not( - .contains-many-editor - ):not(.links-to-editor) + .boxel-field + > .content + > *:not(.links-to-many-editor):not(.contains-many-editor):not( + .links-to-editor + ) ) { padding-right: var(--boxel-icon-lg); } + .default-card-template.edit + > :deep(.boxel-field > .content > *:not(.links-to-many-editor)) { + padding-left: var(--boxel-icon-lg); + } } diff --git a/packages/base/field-component.gts b/packages/base/field-component.gts index 6cea4103d0..1f33bafa39 100644 --- a/packages/base/field-component.gts +++ b/packages/base/field-component.gts @@ -247,6 +247,7 @@ export function getBoxComponent( @value={{defaultFieldFormats effectiveFormats.fieldDef}} >
:deep(*) { + vertical-align: middle; + } + .compound-field.atom-format { + display: inline; } ; diff --git a/packages/base/links-to-editor.gts b/packages/base/links-to-editor.gts index 39b98d14f8..52571ad678 100644 --- a/packages/base/links-to-editor.gts +++ b/packages/base/links-to-editor.gts @@ -86,10 +86,9 @@ export class LinksToEditor extends GlimmerComponent { @@ -416,12 +410,14 @@ export function getLinksToManyComponent({ {{else}} {{#let (coalesce @format defaultFormats.cardDef) - as |effectiveFormat| + (if (eq @displayContainer false) false true) + as |effectiveFormat displayContainer| }}
@@ -438,20 +435,22 @@ export function getLinksToManyComponent({ {{/if}} diff --git a/packages/base/matrix-event.gts b/packages/base/matrix-event.gts index dd63211641..4813f1be5f 100644 --- a/packages/base/matrix-event.gts +++ b/packages/base/matrix-event.gts @@ -9,7 +9,9 @@ import { APP_BOXEL_CARD_FORMAT, APP_BOXEL_CARDFRAGMENT_MSGTYPE, APP_BOXEL_COMMAND_MSGTYPE, - APP_BOXEL_COMMAND_RESULT_MSGTYPE, + APP_BOXEL_COMMAND_RESULT_EVENT_TYPE, + APP_BOXEL_COMMAND_RESULT_WITH_NO_OUTPUT_MSGTYPE, + APP_BOXEL_COMMAND_RESULT_WITH_OUTPUT_MSGTYPE, APP_BOXEL_MESSAGE_MSGTYPE, APP_BOXEL_ROOM_SKILLS_EVENT_TYPE, } from '@cardstack/runtime-common/matrix-constants'; @@ -146,19 +148,6 @@ export interface CommandMessageContent { }; } -export interface ReactionEvent extends BaseMatrixEvent { - type: 'm.reaction'; - content: ReactionEventContent; -} - -export interface ReactionEventContent { - 'm.relates_to': { - event_id: string; - key: string; - rel_type: 'm.annotation'; - }; -} - export interface CardMessageEvent extends BaseMatrixEvent { type: 'm.room.message'; content: CardMessageContent | CardFragmentContent; @@ -239,8 +228,8 @@ export interface SkillsConfigEvent extends RoomStateEvent { } export interface CommandResultEvent extends BaseMatrixEvent { - type: 'm.room.message'; - content: CommandResultContent; + type: typeof APP_BOXEL_COMMAND_RESULT_EVENT_TYPE; + content: CommandResultWithOutputContent | CommandResultWithNoOutputContent; unsigned: { age: number; transaction_id: string; @@ -249,19 +238,27 @@ export interface CommandResultEvent extends BaseMatrixEvent { }; } -export interface CommandResultContent { - 'm.relates_to'?: { +export interface CommandResultWithOutputContent { + 'm.relates_to': { rel_type: 'm.annotation'; key: string; event_id: string; - 'm.in_reply_to'?: { - event_id: string; - }; }; - formatted_body: string; - body: string; - msgtype: typeof APP_BOXEL_COMMAND_RESULT_MSGTYPE; - result: any; + data: { + cardEventId: string; + // we materialize this field on the server + card?: LooseSingleCardDocument; + }; + msgtype: typeof APP_BOXEL_COMMAND_RESULT_WITH_OUTPUT_MSGTYPE; +} + +export interface CommandResultWithNoOutputContent { + 'm.relates_to': { + rel_type: 'm.annotation'; + key: string; + event_id: string; + }; + msgtype: typeof APP_BOXEL_COMMAND_RESULT_WITH_NO_OUTPUT_MSGTYPE; } export type MatrixEvent = @@ -271,7 +268,6 @@ export type MatrixEvent = | MessageEvent | CommandEvent | CommandResultEvent - | ReactionEvent | CardMessageEvent | RoomNameEvent | RoomTopicEvent diff --git a/packages/experiments-realm/AtomExamples/d7aa387b-6514-47c0-ace2-7de011453e51.json b/packages/experiments-realm/AtomExamples/d7aa387b-6514-47c0-ace2-7de011453e51.json new file mode 100644 index 0000000000..3d537da72e --- /dev/null +++ b/packages/experiments-realm/AtomExamples/d7aa387b-6514-47c0-ace2-7de011453e51.json @@ -0,0 +1,217 @@ +{ + "data": { + "type": "card", + "attributes": { + "name": "Lila", + "names": [ + "Evie", + "Lila", + "Marco" + ], + "date": "2024-12-04", + "dates": [ + "2024-12-11", + "2024-12-25", + "2024-12-31" + ], + "trip": { + "title": "Trip 1" + }, + "trips": [ + { + "title": "Trip 1" + }, + { + "title": "Trip 2" + }, + { + "title": "Trip 3" + } + ], + "contactLink": { + "label": "Email", + "value": "a@email.com" + }, + "contactLinks": [ + { + "label": "Email", + "value": "a@email.com" + }, + { + "label": "Phone", + "value": "13472345678" + }, + { + "label": "Other", + "value": "mywebsite-boxel.io" + } + ], + "title": "Samples 1", + "description": null, + "thumbnailURL": null + }, + "relationships": { + "author": { + "links": { + "self": "../Author/alice-enwunder" + } + }, + "authors.0": { + "links": { + "self": "../Author/alice-enwunder" + } + }, + "authors.1": { + "links": { + "self": "../Author/0b9c06fd-3833-4947-a0b8-ac24b8e71ee7" + } + }, + "authors.2": { + "links": { + "self": "../Author/jane-doe" + } + }, + "pet": { + "links": { + "self": "../Pet/2" + } + }, + "pets.0": { + "links": { + "self": "../Pet/2" + } + }, + "pets.1": { + "links": { + "self": "../Pet/1" + } + }, + "pets.2": { + "links": { + "self": "../Pet/3" + } + }, + "trip.country": { + "links": { + "self": "http://localhost:4201/experiments/Country/argentina" + } + }, + "trip.countries.0": { + "links": { + "self": "http://localhost:4201/experiments/Country/brazil" + } + }, + "trip.countries.1": { + "links": { + "self": "http://localhost:4201/experiments/Country/4" + } + }, + "trip.countries.2": { + "links": { + "self": "http://localhost:4201/experiments/Country/1" + } + }, + "trips.0.country": { + "links": { + "self": "../Country/1" + } + }, + "trips.0.countries.0": { + "links": { + "self": "../Country/3" + } + }, + "trips.0.countries.1": { + "links": { + "self": "../Country/4" + } + }, + "trips.1.country": { + "links": { + "self": null + } + }, + "trips.1.countries": { + "links": { + "self": null + } + }, + "trips.2.country": { + "links": { + "self": null + } + }, + "trips.2.countries": { + "links": { + "self": null + } + }, + "teamMember": { + "links": { + "self": "../TeamMember/847c1f4e-e0ae-43d3-a012-8ffaa66f5925" + } + }, + "teamMembers.0": { + "links": { + "self": "../TeamMember/5c42e6e9-59c8-4585-a721-e3414d7e7c9e" + } + }, + "teamMembers.1": { + "links": { + "self": "../TeamMember/03e293b9-e4c1-4592-b8d1-e6f963829086" + } + }, + "teamMembers.2": { + "links": { + "self": "../TeamMember/4c9614cd-ecda-42cf-bc18-37903019a7be" + } + }, + "company": { + "links": { + "self": "../Company/9203c173-3420-44b3-863d-90defd7c57c8" + } + }, + "companies.0": { + "links": { + "self": "../Company/141f3e15-cfd5-4b61-bd4c-37ea31dbeeab" + } + }, + "companies.1": { + "links": { + "self": "../Company/e1856419-27b0-4ce3-98fe-759d21c7144f" + } + }, + "companies.2": { + "links": { + "self": "../Company/9203c173-3420-44b3-863d-90defd7c57c8" + } + }, + "contact": { + "links": { + "self": "../Contact/a01fc5c9-d70d-4b9c-aae4-384cf2b79b25" + } + }, + "contacts.0": { + "links": { + "self": "../Customer/0e5aec99-798b-4417-9426-a338432e0ee5" + } + }, + "contacts.1": { + "links": { + "self": "../Lead/1dbcc3e8-fe3c-4c4a-ba66-ff7d637a7358" + } + }, + "contacts.2": { + "links": { + "self": "../Customer/67e87a6c-00d0-4c48-9318-f0c96daa4cae" + } + } + }, + "meta": { + "adoptsFrom": { + "module": "../atom-examples", + "name": "AtomExamples" + } + } + } +} \ No newline at end of file diff --git a/packages/experiments-realm/CatalogEntry/tag.json b/packages/experiments-realm/CatalogEntry/tag.json new file mode 100644 index 0000000000..db4d9831a1 --- /dev/null +++ b/packages/experiments-realm/CatalogEntry/tag.json @@ -0,0 +1,20 @@ +{ + "data": { + "type": "card", + "attributes": { + "title": "Tag", + "description": "Catalog entry for Tag", + "ref": { + "name": "Tag", + "module": "../tag" + }, + "isField": false + }, + "meta": { + "adoptsFrom": { + "module": "https://cardstack.com/base/catalog-entry", + "name": "CatalogEntry" + } + } + } +} diff --git a/packages/experiments-realm/CatalogEntry/team-member.json b/packages/experiments-realm/CatalogEntry/team-member.json new file mode 100644 index 0000000000..0f04ce5802 --- /dev/null +++ b/packages/experiments-realm/CatalogEntry/team-member.json @@ -0,0 +1,20 @@ +{ + "data": { + "type": "card", + "attributes": { + "title": "Team Member", + "description": "Catalog entry for Team Member", + "ref": { + "name": "TeamMember", + "module": "../sprint-task" + }, + "isField": false + }, + "meta": { + "adoptsFrom": { + "module": "https://cardstack.com/base/catalog-entry", + "name": "CatalogEntry" + } + } + } +} diff --git a/packages/experiments-realm/CatalogEntry/team.json b/packages/experiments-realm/CatalogEntry/team.json new file mode 100644 index 0000000000..d259682406 --- /dev/null +++ b/packages/experiments-realm/CatalogEntry/team.json @@ -0,0 +1,20 @@ +{ + "data": { + "type": "card", + "attributes": { + "title": "Team", + "description": "Catalog entry for Team", + "ref": { + "name": "Team", + "module": "../sprint-task" + }, + "isField": false + }, + "meta": { + "adoptsFrom": { + "module": "https://cardstack.com/base/catalog-entry", + "name": "CatalogEntry" + } + } + } +} diff --git a/packages/experiments-realm/Deal/4fdd3053-14f1-4800-827f-d3e2a5c44ae8.json b/packages/experiments-realm/Deal/4fdd3053-14f1-4800-827f-d3e2a5c44ae8.json index 7b5f78fbb1..4da602d5bb 100644 --- a/packages/experiments-realm/Deal/4fdd3053-14f1-4800-827f-d3e2a5c44ae8.json +++ b/packages/experiments-realm/Deal/4fdd3053-14f1-4800-827f-d3e2a5c44ae8.json @@ -74,7 +74,6 @@ } } ], - "title": null, "description": null, "thumbnailURL": "https://picsum.photos/id/500/200/300" }, diff --git a/packages/experiments-realm/Deal/50b65110-8335-4391-84ec-28cc9c0f22b3.json b/packages/experiments-realm/Deal/50b65110-8335-4391-84ec-28cc9c0f22b3.json index e20a03b726..5516b1dc1f 100644 --- a/packages/experiments-realm/Deal/50b65110-8335-4391-84ec-28cc9c0f22b3.json +++ b/packages/experiments-realm/Deal/50b65110-8335-4391-84ec-28cc9c0f22b3.json @@ -2,7 +2,7 @@ "data": { "type": "card", "attributes": { - "name": null, + "name": "", "status": { "index": 1, "label": "Proposal", @@ -37,7 +37,6 @@ "healthScore": null, "notes": "", "valueBreakdown": [], - "title": null, "description": null, "thumbnailURL": null }, diff --git a/packages/experiments-realm/Deal/f0d769bd-e347-4203-9a96-89332cfba0e2.json b/packages/experiments-realm/Deal/f0d769bd-e347-4203-9a96-89332cfba0e2.json index cd89a90648..c6c44d92f7 100644 --- a/packages/experiments-realm/Deal/f0d769bd-e347-4203-9a96-89332cfba0e2.json +++ b/packages/experiments-realm/Deal/f0d769bd-e347-4203-9a96-89332cfba0e2.json @@ -2,7 +2,7 @@ "data": { "type": "card", "attributes": { - "name": null, + "name": "", "status": { "index": 3, "label": "Closed Won", @@ -37,7 +37,6 @@ "healthScore": null, "notes": null, "valueBreakdown": [], - "title": null, "description": null, "thumbnailURL": null }, diff --git a/packages/experiments-realm/Project/51ca069b-1c55-4648-8654-bd82bf162f9d.json b/packages/experiments-realm/Project/51ca069b-1c55-4648-8654-bd82bf162f9d.json index d157f084e8..d30fa2fefc 100644 --- a/packages/experiments-realm/Project/51ca069b-1c55-4648-8654-bd82bf162f9d.json +++ b/packages/experiments-realm/Project/51ca069b-1c55-4648-8654-bd82bf162f9d.json @@ -8,7 +8,7 @@ }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "Project" } } diff --git a/packages/experiments-realm/Project/9b43240d-8c9c-42bc-a4d1-f9c626661562.json b/packages/experiments-realm/Project/9b43240d-8c9c-42bc-a4d1-f9c626661562.json index 5db9830baf..4e1e461909 100644 --- a/packages/experiments-realm/Project/9b43240d-8c9c-42bc-a4d1-f9c626661562.json +++ b/packages/experiments-realm/Project/9b43240d-8c9c-42bc-a4d1-f9c626661562.json @@ -8,7 +8,7 @@ }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "Project" } } diff --git a/packages/experiments-realm/SprintPlanner/89209d31-328a-4c18-9758-53eb3c38d4f0.json b/packages/experiments-realm/SprintPlanner/89209d31-328a-4c18-9758-53eb3c38d4f0.json index 98ca79b863..27030b89d9 100644 --- a/packages/experiments-realm/SprintPlanner/89209d31-328a-4c18-9758-53eb3c38d4f0.json +++ b/packages/experiments-realm/SprintPlanner/89209d31-328a-4c18-9758-53eb3c38d4f0.json @@ -2,15 +2,7 @@ "data": { "type": "card", "attributes": { - "tabs": [], - "headerIcon": { - "altText": null, - "size": "actual", - "height": null, - "width": null, - "base64": null - }, - "moduleId": null, + "title": "Engineering Team Sprint", "description": null, "thumbnailURL": null }, diff --git a/packages/experiments-realm/SprintTask/10402eaa-3826-4602-994b-60b7506fb98d.json b/packages/experiments-realm/SprintTask/10402eaa-3826-4602-994b-60b7506fb98d.json index ef2612133e..831a182826 100644 --- a/packages/experiments-realm/SprintTask/10402eaa-3826-4602-994b-60b7506fb98d.json +++ b/packages/experiments-realm/SprintTask/10402eaa-3826-4602-994b-60b7506fb98d.json @@ -3,10 +3,10 @@ "type": "card", "attributes": { "status": { - "completed": false, "index": 1, "label": "Next Sprint", - "color": "#64B5F6" + "color": "#64B5F6", + "completed": false }, "dateRange": { "start": "2024-12-08", @@ -16,8 +16,8 @@ "index": 1, "label": "Medium" }, - "name": "Pill Picker Example", - "details": "Use multi-select but for pills", + "name": "Create Pill Picker Example", + "details": "Implementation of a multi-select component using pills UI pattern, intended for use in the multi-filter feature. Users can select multiple pills simultaneously.\n\n**Note:** *This ticket is no longer relevant*", "description": null, "thumbnailURL": null }, @@ -39,28 +39,18 @@ }, "assignee": { "links": { - "self": null + "self": "../TeamMember/f832cfb6-959e-4d0d-bc83-8712a36a910a" } }, "tags.0": { - "links": { - "self": "../Tag/ad921cba-ffc7-4fdb-af34-ab8b93eae228" - } - }, - "tags.1": { "links": { "self": "../Tag/6b8446c8-b185-4de9-ba81-88f8b9eee7f5" } - }, - "tags.2": { - "links": { - "self": "../Tag/f00a2177-df10-4ad7-8eac-2fbf8448de98" - } } }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "SprintTask" } } diff --git a/packages/experiments-realm/SprintTask/186c797c-cee8-449c-a5a7-6624052deefc.json b/packages/experiments-realm/SprintTask/186c797c-cee8-449c-a5a7-6624052deefc.json index a807236011..f2779bcf6f 100644 --- a/packages/experiments-realm/SprintTask/186c797c-cee8-449c-a5a7-6624052deefc.json +++ b/packages/experiments-realm/SprintTask/186c797c-cee8-449c-a5a7-6624052deefc.json @@ -3,14 +3,14 @@ "type": "card", "attributes": { "status": { - "completed": false, - "index": 2, - "label": "Current Sprint", - "color": "#00BCD4" + "index": 4, + "label": "In Review", + "color": "#9575CD", + "completed": false }, "dateRange": { - "start": "2024-10-09", - "end": "2024-10-11" + "start": "2024-10-10", + "end": "2025-01-09" }, "priority": { "index": null, @@ -44,7 +44,7 @@ }, "assignee": { "links": { - "self": "../TeamMember/f832cfb6-959e-4d0d-bc83-8712a36a910a" + "self": "../TeamMember/5c42e6e9-59c8-4585-a721-e3414d7e7c9e" } }, "tags.0": { @@ -60,7 +60,7 @@ }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "SprintTask" } } diff --git a/packages/experiments-realm/SprintTask/1b1fb4fd-d722-4cf9-a956-66fc64ccbc48.json b/packages/experiments-realm/SprintTask/1b1fb4fd-d722-4cf9-a956-66fc64ccbc48.json index 6f18fdfbf4..4dc6c0fc9e 100644 --- a/packages/experiments-realm/SprintTask/1b1fb4fd-d722-4cf9-a956-66fc64ccbc48.json +++ b/packages/experiments-realm/SprintTask/1b1fb4fd-d722-4cf9-a956-66fc64ccbc48.json @@ -3,26 +3,21 @@ "type": "card", "attributes": { "status": { - "completed": false, "index": 2, "label": "Current Sprint", - "color": "#00BCD4" + "color": "#00BCD4", + "completed": false }, "dateRange": { - "start": "2024-11-18", - "end": "2024-11-19" + "start": "2024-12-19", + "end": "2025-01-09" }, "priority": { - "index": null, - "label": null, - "color": null, - "colorScheme": { - "foregroundColor": null, - "backgroundColor": null - } + "index": 2, + "label": "Medium" }, - "name": "Embedded Template for Status and Priority", - "details": null, + "name": "Implement Status and Priority Template Component with Dynamic Rendering", + "details": "Technical implementation of a reusable template component for displaying status and priority indicators across the application. Requirements include:\n- Create base template structure\n- Implement dynamic color rendering based on status\n- Add priority level visualization\n- Ensure accessibility compliance\n- Add unit tests for component", "description": null, "thumbnailURL": null }, @@ -55,7 +50,7 @@ }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "SprintTask" } } diff --git a/packages/experiments-realm/SprintTask/20058a9d-3c2d-4b42-8430-a95b6183245c.json b/packages/experiments-realm/SprintTask/20058a9d-3c2d-4b42-8430-a95b6183245c.json index 3150d7ebd1..976286cce0 100644 --- a/packages/experiments-realm/SprintTask/20058a9d-3c2d-4b42-8430-a95b6183245c.json +++ b/packages/experiments-realm/SprintTask/20058a9d-3c2d-4b42-8430-a95b6183245c.json @@ -3,10 +3,10 @@ "type": "card", "attributes": { "status": { - "completed": false, - "index": 2, - "label": "Current Sprint", - "color": "#00BCD4" + "index": 5, + "label": "Staged", + "color": "#26A69A", + "completed": false }, "dateRange": { "start": "2024-11-06", @@ -16,8 +16,8 @@ "index": 1, "label": "Medium" }, - "name": "Port the already done work for CRM from fork into boxel ui design system", - "details": "https://github.com/tintinthong/boxel/pull/1 CRM for reference. This is the PR that is constantly merged in", + "name": "Port the already done work for CRM into the monorepo", + "details": "Copy the existing work that has been carried out through spikes and git commit to our monorepo", "description": null, "thumbnailURL": null }, @@ -32,14 +32,9 @@ "self": "../Team/cc574556-4253-44b8-ba80-0cfb844b4908" } }, - "subtasks.0": { + "subtasks": { "links": { - "self": "./20058a9d-3c2d-4b42-8430-a95b6183245c" - } - }, - "subtasks.1": { - "links": { - "self": "./9e23b8b5-d541-48ef-a652-e7afefb29777" + "self": null } }, "assignee": { @@ -65,7 +60,7 @@ }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "SprintTask" } } diff --git a/packages/experiments-realm/SprintTask/346cc7ac-eb65-41fd-a1d7-0400668da097.json b/packages/experiments-realm/SprintTask/346cc7ac-eb65-41fd-a1d7-0400668da097.json index bf019d15fb..3052a78d24 100644 --- a/packages/experiments-realm/SprintTask/346cc7ac-eb65-41fd-a1d7-0400668da097.json +++ b/packages/experiments-realm/SprintTask/346cc7ac-eb65-41fd-a1d7-0400668da097.json @@ -3,10 +3,10 @@ "type": "card", "attributes": { "status": { - "completed": false, "index": 3, "label": "In Progress", - "color": "#FFB74D" + "color": "#FFB74D", + "completed": false }, "dateRange": { "start": "2024-11-19", @@ -17,7 +17,7 @@ "label": "High" }, "name": "Make fitted / embeddedd view for task", - "details": null, + "details": "Implement a responsive task view that adapts to its container size using container queries (@container), allowing tasks to be embedded in different contexts while maintaining proper layout and readability. This will ensure consistent display whether tasks appear in boards, lists, or as embedded components.", "description": null, "thumbnailURL": null }, @@ -39,18 +39,18 @@ }, "assignee": { "links": { - "self": null + "self": "../TeamMember/4c9614cd-ecda-42cf-bc18-37903019a7be" } }, - "tags": { + "tags.0": { "links": { - "self": null + "self": "../Tag/f00a2177-df10-4ad7-8eac-2fbf8448de98" } } }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "SprintTask" } } diff --git a/packages/experiments-realm/SprintTask/3cd737c3-4157-46be-9880-6ead8ea60652.json b/packages/experiments-realm/SprintTask/3cd737c3-4157-46be-9880-6ead8ea60652.json index dd7adb9eee..2ab10341a2 100644 --- a/packages/experiments-realm/SprintTask/3cd737c3-4157-46be-9880-6ead8ea60652.json +++ b/packages/experiments-realm/SprintTask/3cd737c3-4157-46be-9880-6ead8ea60652.json @@ -3,10 +3,10 @@ "type": "card", "attributes": { "status": { - "completed": false, - "index": 0, - "label": "Not Started", - "color": "#B0BEC5" + "index": 6, + "label": "Shipped", + "color": "#66BB6A", + "completed": true }, "dateRange": { "start": "2024-11-06", @@ -16,8 +16,8 @@ "index": null, "label": null }, - "name": "Create kanban board", - "details": null, + "name": "Build Kanban Board Component", + "details": "Implement drag and drop functionality using ember-modifier-drag-and-drop:\n\n- Install addon and implement TypeScript interfaces for events/data transfer\n- Create reusable drag/drop modifiers with visual feedback and preview\n- Add validation logic and write tests", "description": null, "thumbnailURL": null }, @@ -50,7 +50,7 @@ }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "SprintTask" } } diff --git a/packages/experiments-realm/SprintTask/48630c9d-2849-43d1-b356-890b016fffe9.json b/packages/experiments-realm/SprintTask/48630c9d-2849-43d1-b356-890b016fffe9.json index 1b726fed81..59c53d3917 100644 --- a/packages/experiments-realm/SprintTask/48630c9d-2849-43d1-b356-890b016fffe9.json +++ b/packages/experiments-realm/SprintTask/48630c9d-2849-43d1-b356-890b016fffe9.json @@ -3,21 +3,21 @@ "type": "card", "attributes": { "status": { - "completed": false, - "index": 3, - "label": "In Progress", - "color": "#FFB74D" + "index": 2, + "label": "Current Sprint", + "color": "#00BCD4", + "completed": false }, "dateRange": { "start": null, "end": null }, "priority": { - "index": null, - "label": null + "index": 4, + "label": "Highest" }, "name": "Fill up task board with realistic data", - "details": null, + "details": "### Objective\nPopulate the task board with realistic sample data that demonstrates the full capabilities of our markdown and task tracking features.\n\n#### Requirements\n- [ ] Add variety of task statuses (Todo, In Progress, Done)\n- [ ] Include tasks with different priority levels\n- [ ] Create sample tasks with rich markdown content\n- [ ] Add realistic date ranges and deadlines", "description": null, "thumbnailURL": null }, @@ -42,15 +42,15 @@ "self": "../TeamMember/03e293b9-e4c1-4592-b8d1-e6f963829086" } }, - "tags": { + "tags.0": { "links": { - "self": null + "self": "../Tag/6d80a75f-71bb-474f-979d-ef4bc571ad63" } } }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "SprintTask" } } diff --git a/packages/experiments-realm/SprintTask/7d92636a-e306-4433-93c5-3b5d976986f3.json b/packages/experiments-realm/SprintTask/7d92636a-e306-4433-93c5-3b5d976986f3.json index 11aced6955..a5229e4f23 100644 --- a/packages/experiments-realm/SprintTask/7d92636a-e306-4433-93c5-3b5d976986f3.json +++ b/packages/experiments-realm/SprintTask/7d92636a-e306-4433-93c5-3b5d976986f3.json @@ -3,21 +3,21 @@ "type": "card", "attributes": { "status": { - "index": 0, - "label": "Not Started", - "color": "#B0BEC5", + "index": 4, + "label": "In Review", + "color": "#9575CD", "completed": false }, "dateRange": { "start": "2024-11-20", - "end": "2024-11-21" + "end": "2024-11-27" }, "priority": { "index": null, "label": null }, - "name": "Drag and drop task", - "details": null, + "name": "Implement Cross-Component Drag and Drop System", + "details": "Design and implement a robust drag and drop system with the following specifications:\n\n- Evaluate and integrate a suitable drag and drop library (e.g., react-dnd, dnd-kit, or sortablejs)\n- Create reusable drag and drop hooks/utilities\n- Implement drag source and drop target abstractions\n- Add visual feedback during drag operations\n- Handle drag preview with custom styling\n- Implement drop validation logic\n- Add support for touch devices\n- Create comprehensive test suite for DnD interactions\n- Document library integration and custom implementation details", "description": null, "thumbnailURL": null }, @@ -50,9 +50,9 @@ }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "SprintTask" } } } -} \ No newline at end of file +} diff --git a/packages/experiments-realm/SprintTask/8ca45d40-adcb-437f-83ee-07ffd973cd17.json b/packages/experiments-realm/SprintTask/8ca45d40-adcb-437f-83ee-07ffd973cd17.json index 9ea0c99eb9..649d317a2f 100644 --- a/packages/experiments-realm/SprintTask/8ca45d40-adcb-437f-83ee-07ffd973cd17.json +++ b/packages/experiments-realm/SprintTask/8ca45d40-adcb-437f-83ee-07ffd973cd17.json @@ -3,10 +3,10 @@ "type": "card", "attributes": { "status": { - "completed": false, - "index": 3, - "label": "In Progress", - "color": "#FFB74D" + "index": 2, + "label": "Current Sprint", + "color": "#00BCD4", + "completed": false }, "dateRange": { "start": null, @@ -17,7 +17,7 @@ "label": null }, "name": "Resolve bug where overlay only appears on certain cards", - "details": null, + "details": "The overlay is only appearing on specific cards due to a faulty modifier that's not being applied consistently across all card instances.", "description": null, "thumbnailURL": null }, @@ -39,18 +39,18 @@ }, "assignee": { "links": { - "self": null + "self": "../TeamMember/5c42e6e9-59c8-4585-a721-e3414d7e7c9e" } }, - "tags": { + "tags.0": { "links": { - "self": null + "self": "../Tag/c3b47bc8-a7df-48c3-8b7b-50dacbf124fc" } } }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "SprintTask" } } diff --git a/packages/experiments-realm/SprintTask/9e23b8b5-d541-48ef-a652-e7afefb29777.json b/packages/experiments-realm/SprintTask/9e23b8b5-d541-48ef-a652-e7afefb29777.json index 8c83e02911..44206a770e 100644 --- a/packages/experiments-realm/SprintTask/9e23b8b5-d541-48ef-a652-e7afefb29777.json +++ b/packages/experiments-realm/SprintTask/9e23b8b5-d541-48ef-a652-e7afefb29777.json @@ -17,7 +17,7 @@ "label": null }, "name": "Incoporate Boxel Multi Select in Dropdown", - "details": null, + "details": "Integrate Boxel's multi-select dropdown component to be more unified with the single-select dropdown.\n\nThe trigger component should share the same designs.", "description": null, "thumbnailURL": null }, @@ -50,7 +50,7 @@ }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "SprintTask" } } diff --git a/packages/experiments-realm/SprintTask/c734e2c7-71d9-446e-a2c1-e6e930fcee55.json b/packages/experiments-realm/SprintTask/a1fd93a9-6b00-4b79-8d97-6c34c3a19cc3.json similarity index 60% rename from packages/experiments-realm/SprintTask/c734e2c7-71d9-446e-a2c1-e6e930fcee55.json rename to packages/experiments-realm/SprintTask/a1fd93a9-6b00-4b79-8d97-6c34c3a19cc3.json index 8ba75e2e92..0e0b089c0b 100644 --- a/packages/experiments-realm/SprintTask/c734e2c7-71d9-446e-a2c1-e6e930fcee55.json +++ b/packages/experiments-realm/SprintTask/a1fd93a9-6b00-4b79-8d97-6c34c3a19cc3.json @@ -3,9 +3,9 @@ "type": "card", "attributes": { "status": { - "index": 3, - "label": "In Progress", - "color": "#FFB74D", + "index": 1, + "label": "Next Sprint", + "color": null, "completed": false }, "dateRange": { @@ -13,10 +13,10 @@ "end": null }, "priority": { - "index": 0, - "label": "Low" + "index": 4, + "label": "Highest" }, - "name": "Add functionality where + opens create card modal with correct status", + "name": "Calendar displays wrong date after closing", "details": null, "description": null, "thumbnailURL": null @@ -24,12 +24,12 @@ "relationships": { "project": { "links": { - "self": "../Project/9b43240d-8c9c-42bc-a4d1-f9c626661562" + "self": "../Project/51ca069b-1c55-4648-8654-bd82bf162f9d" } }, "team": { "links": { - "self": "../Team/cc574556-4253-44b8-ba80-0cfb844b4908" + "self": null } }, "subtasks": { @@ -39,18 +39,18 @@ }, "assignee": { "links": { - "self": "../TeamMember/4c9614cd-ecda-42cf-bc18-37903019a7be" + "self": null } }, - "tags": { + "tags.0": { "links": { - "self": null + "self": "../Tag/c3b47bc8-a7df-48c3-8b7b-50dacbf124fc" } } }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "SprintTask" } } diff --git a/packages/experiments-realm/SprintTask/d21d407b-9c1c-4204-a3bb-7b91f9260017.json b/packages/experiments-realm/SprintTask/d21d407b-9c1c-4204-a3bb-7b91f9260017.json index 8af6ba7a4b..0c59d1133f 100644 --- a/packages/experiments-realm/SprintTask/d21d407b-9c1c-4204-a3bb-7b91f9260017.json +++ b/packages/experiments-realm/SprintTask/d21d407b-9c1c-4204-a3bb-7b91f9260017.json @@ -50,9 +50,9 @@ }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "SprintTask" } } } -} \ No newline at end of file +} diff --git a/packages/experiments-realm/SprintTask/d8ce7466-3693-4d54-8931-8ccd25ca079f.json b/packages/experiments-realm/SprintTask/d8ce7466-3693-4d54-8931-8ccd25ca079f.json index 7b0ec1cd57..0376341087 100644 --- a/packages/experiments-realm/SprintTask/d8ce7466-3693-4d54-8931-8ccd25ca079f.json +++ b/packages/experiments-realm/SprintTask/d8ce7466-3693-4d54-8931-8ccd25ca079f.json @@ -13,11 +13,11 @@ "end": "2024-12-17" }, "priority": { - "index": null, - "label": null + "index": 0, + "label": "Lowest" }, "name": "Build Date Range Card", - "details": null, + "details": "", "description": null, "thumbnailURL": null }, @@ -32,9 +32,14 @@ "self": "../Team/0a799af4-c49a-4d27-aabe-9ed54b9baf55" } }, - "subtasks": { + "subtasks.0": { + "links": { + "self": "./a1fd93a9-6b00-4b79-8d97-6c34c3a19cc3" + } + }, + "subtasks.1": { "links": { - "self": null + "self": "./d9fdfbbc-fb17-41d9-90a2-c5765819930b" } }, "assignee": { @@ -42,17 +47,17 @@ "self": "../TeamMember/03e293b9-e4c1-4592-b8d1-e6f963829086" } }, - "tags": { + "tags.0": { "links": { - "self": null + "self": "../Tag/f00a2177-df10-4ad7-8eac-2fbf8448de98" } } }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "SprintTask" } } } -} \ No newline at end of file +} diff --git a/packages/experiments-realm/SprintTask/d9fdfbbc-fb17-41d9-90a2-c5765819930b.json b/packages/experiments-realm/SprintTask/d9fdfbbc-fb17-41d9-90a2-c5765819930b.json new file mode 100644 index 0000000000..d230492914 --- /dev/null +++ b/packages/experiments-realm/SprintTask/d9fdfbbc-fb17-41d9-90a2-c5765819930b.json @@ -0,0 +1,58 @@ +{ + "data": { + "type": "card", + "attributes": { + "status": { + "index": 1, + "label": "Next Sprint", + "color": null, + "completed": false + }, + "dateRange": { + "start": "2025-01-09", + "end": "2025-01-12" + }, + "priority": { + "index": null, + "label": null + }, + "name": "Integrate Ember Power Calendar Picker", + "details": "Create a date range picker component using Ember Power Calendar with built-in validation to prevent end dates being set before start dates. Component should be implemented within a dropdown for better UX, following this pattern:\n\n1. Click triggers dropdown with calendar\n2. First click sets start date\n3. Second click sets end date (with validation)\n4. Invalid date ranges should be prevented and show validation feedback\n\n**Technical Requirements:**\n- Use `ember-power-calendar` for the calendar implementation\n- Implement date validation logic in the component\n- Style dropdown to match our design system", + "description": null, + "thumbnailURL": null + }, + "relationships": { + "project": { + "links": { + "self": "../Project/51ca069b-1c55-4648-8654-bd82bf162f9d" + } + }, + "team": { + "links": { + "self": null + } + }, + "subtasks": { + "links": { + "self": null + } + }, + "assignee": { + "links": { + "self": null + } + }, + "tags": { + "links": { + "self": null + } + } + }, + "meta": { + "adoptsFrom": { + "module": "../sprint-task", + "name": "SprintTask" + } + } + } +} diff --git a/packages/experiments-realm/SprintTask/f81ea9a1-0b94-4e12-8e2e-c84fba78c551.json b/packages/experiments-realm/SprintTask/f81ea9a1-0b94-4e12-8e2e-c84fba78c551.json index b5f6791058..c625a07921 100644 --- a/packages/experiments-realm/SprintTask/f81ea9a1-0b94-4e12-8e2e-c84fba78c551.json +++ b/packages/experiments-realm/SprintTask/f81ea9a1-0b94-4e12-8e2e-c84fba78c551.json @@ -50,7 +50,7 @@ }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "SprintTask" } } diff --git a/packages/experiments-realm/Tag/c3b47bc8-a7df-48c3-8b7b-50dacbf124fc.json b/packages/experiments-realm/Tag/c3b47bc8-a7df-48c3-8b7b-50dacbf124fc.json new file mode 100644 index 0000000000..4c7f6196c4 --- /dev/null +++ b/packages/experiments-realm/Tag/c3b47bc8-a7df-48c3-8b7b-50dacbf124fc.json @@ -0,0 +1,17 @@ +{ + "data": { + "type": "card", + "attributes": { + "name": "Bug", + "color": "#FFCCCB", + "description": null, + "thumbnailURL": null + }, + "meta": { + "adoptsFrom": { + "module": "../tag", + "name": "Tag" + } + } + } +} \ No newline at end of file diff --git a/packages/experiments-realm/Team/0a799af4-c49a-4d27-aabe-9ed54b9baf55.json b/packages/experiments-realm/Team/0a799af4-c49a-4d27-aabe-9ed54b9baf55.json index 4719eab7b2..ded61fd31b 100644 --- a/packages/experiments-realm/Team/0a799af4-c49a-4d27-aabe-9ed54b9baf55.json +++ b/packages/experiments-realm/Team/0a799af4-c49a-4d27-aabe-9ed54b9baf55.json @@ -8,9 +8,9 @@ }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "Team" } } } -} \ No newline at end of file +} diff --git a/packages/experiments-realm/Team/cc574556-4253-44b8-ba80-0cfb844b4908.json b/packages/experiments-realm/Team/cc574556-4253-44b8-ba80-0cfb844b4908.json index 6032ebd96a..fc3e5ca3c3 100644 --- a/packages/experiments-realm/Team/cc574556-4253-44b8-ba80-0cfb844b4908.json +++ b/packages/experiments-realm/Team/cc574556-4253-44b8-ba80-0cfb844b4908.json @@ -8,9 +8,9 @@ }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "Team" } } } -} \ No newline at end of file +} diff --git a/packages/experiments-realm/TeamMember/03e293b9-e4c1-4592-b8d1-e6f963829086.json b/packages/experiments-realm/TeamMember/03e293b9-e4c1-4592-b8d1-e6f963829086.json index f06ead1d06..a96a2f91be 100644 --- a/packages/experiments-realm/TeamMember/03e293b9-e4c1-4592-b8d1-e6f963829086.json +++ b/packages/experiments-realm/TeamMember/03e293b9-e4c1-4592-b8d1-e6f963829086.json @@ -16,7 +16,7 @@ }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "TeamMember" } } diff --git a/packages/experiments-realm/TeamMember/4c9614cd-ecda-42cf-bc18-37903019a7be.json b/packages/experiments-realm/TeamMember/4c9614cd-ecda-42cf-bc18-37903019a7be.json index 247108b2df..8c1fa2b79d 100644 --- a/packages/experiments-realm/TeamMember/4c9614cd-ecda-42cf-bc18-37903019a7be.json +++ b/packages/experiments-realm/TeamMember/4c9614cd-ecda-42cf-bc18-37903019a7be.json @@ -16,9 +16,9 @@ }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "TeamMember" } } } -} \ No newline at end of file +} diff --git a/packages/experiments-realm/TeamMember/5c42e6e9-59c8-4585-a721-e3414d7e7c9e.json b/packages/experiments-realm/TeamMember/5c42e6e9-59c8-4585-a721-e3414d7e7c9e.json index 96839e818b..32c36b206b 100644 --- a/packages/experiments-realm/TeamMember/5c42e6e9-59c8-4585-a721-e3414d7e7c9e.json +++ b/packages/experiments-realm/TeamMember/5c42e6e9-59c8-4585-a721-e3414d7e7c9e.json @@ -16,9 +16,9 @@ }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "TeamMember" } } } -} \ No newline at end of file +} diff --git a/packages/experiments-realm/TeamMember/847c1f4e-e0ae-43d3-a012-8ffaa66f5925.json b/packages/experiments-realm/TeamMember/847c1f4e-e0ae-43d3-a012-8ffaa66f5925.json index ff921dbde5..61eff946d8 100644 --- a/packages/experiments-realm/TeamMember/847c1f4e-e0ae-43d3-a012-8ffaa66f5925.json +++ b/packages/experiments-realm/TeamMember/847c1f4e-e0ae-43d3-a012-8ffaa66f5925.json @@ -16,9 +16,9 @@ }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "TeamMember" } } } -} \ No newline at end of file +} diff --git a/packages/experiments-realm/TeamMember/f832cfb6-959e-4d0d-bc83-8712a36a910a.json b/packages/experiments-realm/TeamMember/f832cfb6-959e-4d0d-bc83-8712a36a910a.json index c0963ed97a..098ecf72e8 100644 --- a/packages/experiments-realm/TeamMember/f832cfb6-959e-4d0d-bc83-8712a36a910a.json +++ b/packages/experiments-realm/TeamMember/f832cfb6-959e-4d0d-bc83-8712a36a910a.json @@ -16,9 +16,9 @@ }, "meta": { "adoptsFrom": { - "module": "../productivity/task", + "module": "../sprint-task", "name": "TeamMember" } } } -} \ No newline at end of file +} diff --git a/packages/experiments-realm/atom-examples.gts b/packages/experiments-realm/atom-examples.gts new file mode 100644 index 0000000000..7e5d03ac98 --- /dev/null +++ b/packages/experiments-realm/atom-examples.gts @@ -0,0 +1,225 @@ +import { on } from '@ember/modifier'; +import { tracked } from '@glimmer/tracking'; +import { + contains, + containsMany, + field, + linksTo, + linksToMany, + CardDef, + Component, + FieldDef, + StringField, +} from 'https://cardstack.com/base/card-api'; +import DateField from 'https://cardstack.com/base/date'; +import AtomIcon from '@cardstack/boxel-icons/atom'; +import { Button } from '@cardstack/boxel-ui/components'; +import { Author } from './author'; +import { Pet } from './pet'; +import { Country } from './country'; +import { TeamMember } from './sprint-task'; +import { Company } from './crm/company'; +import { Contact } from './crm/contact'; +import { ContactLinkField } from './fields/contact-link'; + +class Isolated extends Component { + + + @tracked displayContainer = false; + toggleContainer = () => { + this.displayContainer = !this.displayContainer; + }; +} + +class Trip extends FieldDef { + static displayName = 'Trip'; + @field title = contains(StringField); + @field country = linksTo(Country); + @field countries = linksToMany(Country); +} + +export class AtomExamples extends CardDef { + static displayName = 'Atom Examples'; + static icon = AtomIcon; + @field name = contains(StringField); + @field names = containsMany(StringField); + @field date = contains(DateField); + @field dates = containsMany(DateField); + @field author = linksTo(Author); + @field authors = linksToMany(Author); + @field pet = linksTo(Pet); + @field pets = linksToMany(Pet); + @field trip = contains(Trip); + @field trips = containsMany(Trip); + @field teamMember = linksTo(TeamMember); + @field teamMembers = linksToMany(TeamMember); + @field company = linksTo(Company); + @field companies = linksToMany(Company); + @field contact = linksTo(Contact); + @field contacts = linksToMany(Contact); + @field contactLink = contains(ContactLinkField); + @field contactLinks = containsMany(ContactLinkField); + + static isolated = Isolated; +} diff --git a/packages/experiments-realm/components/activity-card.gts b/packages/experiments-realm/components/activity-card.gts index b5277ff520..853e9a1819 100644 --- a/packages/experiments-realm/components/activity-card.gts +++ b/packages/experiments-realm/components/activity-card.gts @@ -1,12 +1,8 @@ import GlimmerComponent from '@glimmer/component'; -import EntityDisplayWithThumbnail from './entity-thumbnail-display'; interface ActivityCardArgs { Blocks: { - title?: []; - thumbnail?: []; - description?: []; - icon?: []; + header?: []; content?: []; }; Element: HTMLElement; @@ -15,34 +11,9 @@ interface ActivityCardArgs { export default class ActivityCard extends GlimmerComponent { } diff --git a/packages/experiments-realm/components/entity-icon-display.gts b/packages/experiments-realm/components/entity-icon-display.gts index 73cc4142d1..847d46f8fa 100644 --- a/packages/experiments-realm/components/entity-icon-display.gts +++ b/packages/experiments-realm/components/entity-icon-display.gts @@ -77,10 +77,15 @@ export default class EntityDisplayWithIcon extends GlimmerComponent diff --git a/packages/experiments-realm/components/entity-thumbnail-display.gts b/packages/experiments-realm/components/entity-thumbnail-display.gts index 26aac840e9..0d4f7ce3d7 100644 --- a/packages/experiments-realm/components/entity-thumbnail-display.gts +++ b/packages/experiments-realm/components/entity-thumbnail-display.gts @@ -80,10 +80,15 @@ export default class EntityDisplayWithThumbnail extends GlimmerComponent diff --git a/packages/experiments-realm/productivity/filter-display.gts b/packages/experiments-realm/components/filter/filter-display.gts similarity index 100% rename from packages/experiments-realm/productivity/filter-display.gts rename to packages/experiments-realm/components/filter/filter-display.gts diff --git a/packages/experiments-realm/productivity/filter-dropdown-item.gts b/packages/experiments-realm/components/filter/filter-dropdown-item.gts similarity index 100% rename from packages/experiments-realm/productivity/filter-dropdown-item.gts rename to packages/experiments-realm/components/filter/filter-dropdown-item.gts diff --git a/packages/experiments-realm/productivity/filter-dropdown.gts b/packages/experiments-realm/components/filter/filter-dropdown.gts similarity index 100% rename from packages/experiments-realm/productivity/filter-dropdown.gts rename to packages/experiments-realm/components/filter/filter-dropdown.gts diff --git a/packages/experiments-realm/productivity/filter-trigger.gts b/packages/experiments-realm/components/filter/filter-trigger.gts similarity index 100% rename from packages/experiments-realm/productivity/filter-trigger.gts rename to packages/experiments-realm/components/filter/filter-trigger.gts diff --git a/packages/experiments-realm/crm-app.gts b/packages/experiments-realm/crm-app.gts index ec76d16ae6..9b62afae09 100644 --- a/packages/experiments-realm/crm-app.gts +++ b/packages/experiments-realm/crm-app.gts @@ -23,13 +23,20 @@ import { } from '@cardstack/boxel-ui/components'; import { IconPlus } from '@cardstack/boxel-ui/icons'; import { AppCard, Tab } from './app-card'; -import { Query, CardError, SupportedMimeType } from '@cardstack/runtime-common'; +import { + Query, + CardError, + SupportedMimeType, + getCards, +} from '@cardstack/runtime-common'; import ContactIcon from '@cardstack/boxel-icons/contact'; import HeartHandshakeIcon from '@cardstack/boxel-icons/heart-handshake'; import TargetArrowIcon from '@cardstack/boxel-icons/target-arrow'; import CalendarExclamation from '@cardstack/boxel-icons/calendar-exclamation'; import { urgencyTagValues } from './crm/account'; import { dealStatusValues } from './crm/deal'; +import type { Deal } from './crm/deal'; +import DealSummary from './crm/deal-summary'; type ViewOption = 'card' | 'strip' | 'grid'; @@ -101,12 +108,14 @@ class CrmAppTemplate extends Component { @tracked private activeFilter: LayoutFilter = CONTACT_FILTERS[0]; @action private onFilterChange(filter: LayoutFilter) { this.activeFilter = filter; + this.loadDealCards.perform(); } //tabs @tracked activeTabId: string | undefined = this.args.model.tabs?.[0]?.tabId; @tracked tabs = this.args.model.tabs; @tracked private selectedView: ViewOption = 'card'; @tracked private searchKey = ''; + @tracked private deals: Deal[] = []; constructor(owner: Owner, args: any) { super(owner, args); @@ -152,6 +161,20 @@ class CrmAppTemplate extends Component { } }); + private loadDealCards = restartableTask(async () => { + if (!this.query || this.activeTabId !== 'Deal') { + return; + } + + const result = getCards(this.query, this.realmHrefs, { + isLive: true, + }); + + await result.loaded; + this.deals = result.instances as Deal[]; + return result; + }); + get filters() { return this.filterMap.get(this.activeTabId!)!; } @@ -165,6 +188,7 @@ class CrmAppTemplate extends Component { this.activeTabId = id; this.searchKey = ''; this.setActiveFilter(); + this.loadDealCards.perform(); } get headerColor() { return ( @@ -194,6 +218,9 @@ class CrmAppTemplate extends Component { private get realms() { return [this.currentRealm!]; } + get realmHrefs() { + return [this.currentRealm!.href]; + } //create @action private createNew() { @@ -262,10 +289,6 @@ class CrmAppTemplate extends Component { on: activeFilter.cardRef, contains: { name: searchKey }, }, - { - on: activeFilter.cardRef, - contains: { 'company.name': searchKey }, - }, ], }, ] @@ -360,6 +383,11 @@ class CrmAppTemplate extends Component { {{this.activeFilter.createNewButtonText}} {{/if}} + {{#if (eq this.activeTabId 'Deal')}} +
+ +
+ {{/if}}