Skip to content

Commit 2d98316

Browse files
authored
chore: Refactor shell's view to use same flow for home and default charms (commontoolsinc#2180)
1 parent 4f70ef4 commit 2d98316

File tree

10 files changed

+138
-154
lines changed

10 files changed

+138
-154
lines changed

packages/charm/src/manager.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,22 @@ export class CharmManager {
283283
await this.runtime.idle();
284284
}
285285

286+
/**
287+
* Get the default pattern cell from the space cell.
288+
* @returns The default pattern cell, or undefined if not set
289+
*/
290+
async getDefaultPattern(): Promise<Cell<NameSchema> | undefined> {
291+
const cell = await this.spaceCell.key("defaultPattern").sync();
292+
if (!cell.get().get()) {
293+
return undefined;
294+
}
295+
return this.get(
296+
cell.get(),
297+
true,
298+
nameSchema,
299+
);
300+
}
301+
286302
/**
287303
* Track a charm as recently viewed/interacted with.
288304
* Maintains a list of up to 10 most recent charms.

packages/charm/src/ops/mod.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export { CharmsController } from "./charms-controller.ts";
22
export { ACLManager } from "./acl-manager.ts";
3-
export type { CharmController } from "./charm-controller.ts";
3+
export { CharmController } from "./charm-controller.ts";
44
export * from "./utils.ts";

packages/patterns/counter.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,18 @@ export default recipe<RecipeState, RecipeOutput>((state) => {
1717
[NAME]: str`Simple counter: ${state.value}`,
1818
[UI]: (
1919
<div>
20-
<ct-button onClick={decrement(state)}>
20+
<ct-button id="counter-decrement" onClick={decrement(state)}>
2121
dec to {previous(state.value)}
2222
</ct-button>
2323
<ct-cell-context $cell={state.value} inline>
2424
<span id="counter-result">
2525
Counter is the {nth(state.value)} number
2626
</span>
2727
</ct-cell-context>
28-
<ct-button onClick={increment({ value: state.value })}>
28+
<ct-button
29+
id="counter-increment"
30+
onClick={increment({ value: state.value })}
31+
>
2932
inc to {(state.value ?? 0) + 1}
3033
</ct-button>
3134
</div>

packages/shell/integration/charm.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ describe("shell charm tests", () => {
6060
});
6161

6262
let handle = await page.waitForSelector(
63-
"ct-button",
63+
"#counter-decrement",
6464
{ strategy: "pierce" },
6565
);
6666
handle.click();

packages/shell/integration/iframe-counter-charm.disabled_test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ async function getCharmResult(page: Page): Promise<{ count: number }> {
6565
// Use the element handle to evaluate in its context
6666
return await appView.evaluate((element: XAppView) => {
6767
// Access the private _activeCharm property
68-
const activeCharmTask = element._activeCharm;
68+
const activeCharmTask = element._activePatterns;
6969

7070
if (!activeCharmTask) {
7171
throw new Error("No _activeCharm property found on element");
@@ -76,7 +76,7 @@ async function getCharmResult(page: Page): Promise<{ count: number }> {
7676
}
7777

7878
// Get the charm controller from the Task's value
79-
const charmController = activeCharmTask.value;
79+
const charmController = activeCharmTask.value.activePattern;
8080

8181
// Get the result from the charm controller
8282
const result = charmController.result.get();

packages/shell/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ console.log(`ENVIRONMENT=${ENVIRONMENT}`);
1616
console.log(`API_URL=${API_URL}`);
1717
console.log(`COMMIT_SHA=${COMMIT_SHA}`);
1818

19-
// Configure LLM client to use the correct API URL
2019
setLLMUrl(API_URL.toString());
2120

2221
setRecipeEnvironment({ apiUrl: API_URL });

packages/shell/src/lib/pattern-factory.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,35 @@ export async function create(
3535
new HttpProgramResolver(config.url.href),
3636
);
3737

38-
const charm = await cc.create(program, undefined, config.cause);
38+
const charm = await cc.create(program, { start: true }, config.cause);
3939

4040
// Wait for the link to be processed
4141
await runtime.idle();
4242
await manager.synced();
4343

44-
if (type === "space-default") {
45-
// Link the default pattern to the space cell
46-
await manager.linkDefaultPattern(charm.getCell());
47-
}
44+
// Link the default pattern to the space cell
45+
await manager.linkDefaultPattern(charm.getCell());
4846

4947
return charm;
5048
}
5149

52-
export function getPattern(
53-
charms: CharmController[],
50+
export async function get(
51+
cc: CharmsController,
52+
): Promise<CharmController | undefined> {
53+
const pattern = await cc.manager().getDefaultPattern();
54+
if (!pattern) {
55+
return undefined;
56+
}
57+
return new CharmController(cc.manager(), pattern);
58+
}
59+
60+
export async function getOrCreate(
61+
cc: CharmsController,
5462
type: BuiltinPatternType,
55-
): CharmController | undefined {
56-
const config = Configs[type];
57-
return charms.find((c) => {
58-
const name = c.name();
59-
return name && name.startsWith(config.name);
60-
});
63+
): Promise<CharmController> {
64+
const pattern = await get(cc);
65+
if (pattern) {
66+
return pattern;
67+
}
68+
return await create(cc, type);
6169
}

packages/shell/src/views/AppView.ts

Lines changed: 82 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { css, html } from "lit";
22
import { property, state } from "lit/decorators.js";
33

4-
import { AppState } from "../lib/app/mod.ts";
4+
import { AppState, AppView } from "../lib/app/mod.ts";
55
import { BaseView } from "./BaseView.ts";
66
import { KeyStore } from "@commontools/identity";
77
import { RuntimeInternals } from "../lib/runtime.ts";
88
import { DebuggerController } from "../lib/debugger-controller.ts";
99
import "./DebuggerView.ts";
1010
import { Task } from "@lit/task";
11-
import { CharmController } from "@commontools/charm/ops";
11+
import { CharmController, CharmsController } from "@commontools/charm/ops";
1212
import { CellEventTarget, CellUpdateEvent } from "../lib/cell-event-target.ts";
1313
import { NAME } from "@commontools/runner";
1414
import { type NameSchema, nameSchema } from "@commontools/charm";
@@ -129,45 +129,6 @@ export class XAppView extends BaseView {
129129
this.hasSidebarContent = event.detail.hasSidebarContent;
130130
};
131131

132-
// Maps the app level view to a specific charm to load
133-
// as the primary, active charm.
134-
_activeCharmId = new Task(this, {
135-
task: async ([app, rt]): Promise<string | undefined> => {
136-
if (!app || !rt) {
137-
return;
138-
}
139-
if ("builtin" in app.view) {
140-
if (app.view.builtin !== "home") {
141-
console.warn("Unsupported view type");
142-
return;
143-
}
144-
{
145-
await rt.cc().manager().synced();
146-
const pattern = await PatternFactory.getPattern(
147-
rt.cc().getAllCharms(),
148-
"home",
149-
);
150-
if (pattern) {
151-
return pattern.id;
152-
}
153-
}
154-
const pattern = await PatternFactory.create(
155-
rt.cc(),
156-
"home",
157-
);
158-
return pattern.id;
159-
} else if ("spaceDid" in app.view) {
160-
console.warn("Unsupported view type");
161-
} else if ("spaceName" in app.view) {
162-
// eventually, this should load the default pattern
163-
// for a space if needed, but for now is handled
164-
// in BodyView, and only set the active charm ID
165-
// for explicit charms set in the URL.
166-
return app.view.charmId;
167-
}
168-
},
169-
args: () => [this.app, this.rt],
170-
});
171132
private handleCellWatch = (e: Event) => {
172133
const event = e as CustomEvent<{ cell: unknown; label?: string }>;
173134
const { cell, label } = event.detail;
@@ -192,30 +153,42 @@ export class XAppView extends BaseView {
192153
};
193154

194155
// Do not make private, integration tests access this directly.
195-
_activeCharm = new Task(this, {
196-
task: async ([activeCharmId]): Promise<CharmController | undefined> => {
197-
if (!this.rt || !this.app || !activeCharmId) {
156+
//
157+
// This fetches the active pattern and space default pattern derived
158+
// from the current view.
159+
_activePatterns = new Task(this, {
160+
task: async (
161+
[app, rt],
162+
): Promise<
163+
| { activePattern: CharmController; defaultPattern: CharmController }
164+
| undefined
165+
> => {
166+
if (!app || !rt) {
198167
this.#setTitleSubscription();
199168
return;
200169
}
201-
const current: CharmController | undefined = this._activeCharm.value;
202-
if (
203-
current && current.id === activeCharmId
204-
) {
205-
return current;
206-
}
207-
const activeCharm = await this.rt.cc().get(
208-
activeCharmId,
209-
true,
210-
nameSchema,
170+
171+
const patterns = await viewToPatterns(
172+
rt.cc(),
173+
app.view,
174+
this._activePatterns.value?.activePattern,
211175
);
176+
if (!patterns) {
177+
this.#setTitleSubscription();
178+
return;
179+
}
180+
212181
// Record the charm as recently accessed so recents stay fresh.
213-
await this.rt.cc().manager().trackRecentCharm(activeCharm.getCell());
214-
this.#setTitleSubscription(activeCharm);
182+
await rt.cc().manager().trackRecentCharm(
183+
patterns.activePattern.getCell(),
184+
);
185+
this.#setTitleSubscription(
186+
patterns.activePattern as CharmController<NameSchema>,
187+
);
215188

216-
return activeCharm;
189+
return patterns;
217190
},
218-
args: () => [this._activeCharmId.value],
191+
args: () => [this.app, this.rt],
219192
});
220193

221194
#setTitleSubscription(activeCharm?: CharmController<NameSchema>) {
@@ -280,10 +253,14 @@ export class XAppView extends BaseView {
280253
const unauthenticated = html`
281254
<x-login-view .keyStore="${this.keyStore}"></x-login-view>
282255
`;
256+
const patterns = this._activePatterns.value;
257+
const activePattern = patterns?.activePattern;
258+
const defaultPattern = patterns?.defaultPattern;
283259
const authenticated = html`
284260
<x-body-view
285261
.rt="${this.rt}"
286-
.activeCharm="${this._activeCharm.value}"
262+
.activeCharm="${activePattern}"
263+
.defaultCharm="${defaultPattern}"
287264
.showShellCharmListView="${app.showShellCharmListView ?? false}"
288265
.showSidebar="${app.showSidebar ?? false}"
289266
></x-body-view>
@@ -301,7 +278,7 @@ export class XAppView extends BaseView {
301278
.rt="${this.rt}"
302279
.keyStore="${this.keyStore}"
303280
.charmTitle="${this.charmTitle}"
304-
.charmId="${this._activeCharmId.value}"
281+
.charmId="${activePattern?.id}"
305282
.showShellCharmListView="${app.showShellCharmListView ?? false}"
306283
.showDebuggerView="${app.showDebuggerView ?? false}"
307284
.showSidebar="${app.showSidebar ?? false}"
@@ -328,4 +305,49 @@ export class XAppView extends BaseView {
328305
}
329306
}
330307

308+
async function viewToPatterns(
309+
cc: CharmsController,
310+
view: AppView,
311+
currentActive?: CharmController<unknown>,
312+
): Promise<
313+
{
314+
activePattern: CharmController<unknown>;
315+
defaultPattern: CharmController<unknown>;
316+
} | undefined
317+
> {
318+
if ("builtin" in view) {
319+
if (view.builtin !== "home") {
320+
console.warn("Unsupported view type");
321+
return;
322+
}
323+
const pattern = await PatternFactory.getOrCreate(cc, "home");
324+
return { activePattern: pattern, defaultPattern: pattern };
325+
} else if ("spaceDid" in view) {
326+
console.warn("Unsupported view type");
327+
return;
328+
} else if ("spaceName" in view) {
329+
const defaultPattern = await PatternFactory.getOrCreate(
330+
cc,
331+
"space-default",
332+
);
333+
334+
let activePattern: CharmController<unknown> | undefined;
335+
// If viewing a specific charm, use it as active; otherwise use default
336+
if (view.charmId) {
337+
if (currentActive && currentActive.id === view.charmId) {
338+
activePattern = currentActive;
339+
} else {
340+
activePattern = await cc.get(
341+
view.charmId,
342+
true,
343+
nameSchema,
344+
);
345+
}
346+
} else {
347+
activePattern = defaultPattern;
348+
}
349+
return { activePattern, defaultPattern };
350+
}
351+
}
352+
331353
globalThis.customElements.define("x-app-view", XAppView);

0 commit comments

Comments
 (0)