Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions docs/common/LOCAL_DEV_SERVERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,86 @@ curl http://localhost:8000/_health && curl http://localhost:5173
kill $TOOLSHED_PID $SHELL_PID
```

## Reliable Process Shutdown

When PIDs weren't tracked or processes become orphaned, use port-based termination:

### Force-Kill by Port

```bash
# Kill any process on port 8000 (Toolshed)
lsof -ti :8000 | xargs kill -9 2>/dev/null

# Kill any process on port 5173 (Shell)
lsof -ti :5173 | xargs kill -9 2>/dev/null
```

### Verify Ports Are Free

Always verify before restarting:

```bash
# Should return nothing if ports are free
lsof -i :8000
lsof -i :5173
```

### Complete Restart Workflow

```bash
# 1. Stop all processes on both ports
lsof -ti :8000 | xargs kill -9 2>/dev/null
lsof -ti :5173 | xargs kill -9 2>/dev/null

# 2. Wait briefly for cleanup
sleep 2

# 3. Verify ports are free
if lsof -i :8000 || lsof -i :5173; then
echo "ERROR: Ports still in use"
exit 1
fi

# 4. Start servers (in separate terminals or background)
cd packages/toolshed && SHELL_URL=http://localhost:5173 deno task dev &
TOOLSHED_PID=$!

cd packages/shell && deno task dev-local &
SHELL_PID=$!

# 5. Wait for startup
sleep 5

# 6. Verify both are healthy
curl -sf http://localhost:8000/_health > /dev/null && echo "Backend: OK" || echo "Backend: FAILED"
curl -sf http://localhost:5173 > /dev/null && echo "Frontend: OK" || echo "Frontend: FAILED"
```

### Troubleshooting Stubborn Processes

If `lsof -ti :PORT | xargs kill -9` doesn't work:

1. **Check for multiple processes:**
```bash
lsof -i :8000 # Lists all processes with details
```

2. **Kill by process name (nuclear option):**
```bash
pkill -9 -f "deno task dev"
```

3. **Check for child processes:**
```bash
pgrep -f deno | xargs ps -p
```

4. **Last resort - kill all deno processes:**
```bash
pkill -9 -f deno
```
⚠️ This kills ALL deno processes, not just dev servers.

## Integration with Pattern Development

When deploying patterns locally:
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/commands/acl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,6 @@ function parseSpaceOptions(
return {
apiUrl: new URL(apiUrl),
identityPath: identity,
spaceName: space,
space: space,
};
}
18 changes: 11 additions & 7 deletions packages/cli/lib/acl.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createSession, Session } from "@commontools/identity";
import { createSession, isDID, Session } from "@commontools/identity";
import { loadIdentity } from "./identity.ts";
import { Runtime } from "@commontools/runner";
import { StorageManager } from "@commontools/runner/storage/cache";
Expand All @@ -13,17 +13,21 @@ import { ACLManager } from "@commontools/charm/ops";
export interface SpaceConfig {
apiUrl: URL;
identityPath: string;
spaceName: string;
space: string;
}

// Create an identity and session from configuration.
async function loadSession(config: SpaceConfig): Promise<Session> {
const identity = await loadIdentity(config.identityPath);
const session = await createSession({
identity,
spaceName: config.spaceName,
});
return session;
return isDID(config.space)
? createSession({
identity,
spaceDid: config.space,
})
: createSession({
identity,
spaceName: config.space,
});
}

// Creates a Runtime instance for ACL operations
Expand Down
11 changes: 6 additions & 5 deletions packages/cli/lib/charm.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createSession, Session } from "@commontools/identity";
import { createSession, isDID, Session } from "@commontools/identity";
import { ensureDir } from "@std/fs";
import { loadIdentity } from "./identity.ts";
import {
Expand Down Expand Up @@ -32,11 +32,12 @@ export interface CharmConfig extends SpaceConfig {
}

async function makeSession(config: SpaceConfig): Promise<Session> {
if (config.space.startsWith("did:key")) {
throw new Error("DID key spaces not yet supported.");
}
const identity = await loadIdentity(config.identity);
return createSession({ identity, spaceName: config.space });
if (isDID(config.space)) {
return createSession({ identity, spaceDid: config.space });
} else {
return createSession({ identity, spaceName: config.space });
}
}

export async function loadManager(config: SpaceConfig): Promise<CharmManager> {
Expand Down
46 changes: 46 additions & 0 deletions packages/html/src/jsx.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2864,6 +2864,7 @@ interface CTListElement extends CTHTMLElement {}
interface CTListItemElement extends CTHTMLElement {}
interface CTLoaderElement extends CTHTMLElement {}
interface CTInputElement extends CTHTMLElement {}
interface CTTextAreaElement extends CTHTMLElement {}
interface CTFileInputElement extends CTHTMLElement {}
interface CTImageInputElement extends CTHTMLElement {}
interface CTInputLegacyElement extends CTHTMLElement {}
Expand Down Expand Up @@ -3261,6 +3262,47 @@ interface CTInputAttributes<T> extends CTHTMLAttributes<T> {
"onct-invalid"?: any;
}

interface CTTextAreaAttributes<T> extends CTHTMLAttributes<T> {
"$value"?: CellLike<string>;
"value"?: CellLike<string> | string;
"placeholder"?: string;
"disabled"?: boolean;
"readonly"?: boolean;
"error"?: boolean;
"name"?: string;
"required"?: boolean;
"autofocus"?: boolean;
"rows"?: number;
"cols"?: number;
"maxlength"?: string;
"minlength"?: string;
"wrap"?: string;
"spellcheck"?: boolean;
"autocomplete"?: string;
"resize"?: string;
"auto-resize"?: boolean;
"timing-strategy"?: "immediate" | "debounce" | "throttle" | "blur";
"timing-delay"?: number;
"onct-input"?: EventHandler<
{ value: string; oldValue: string; name: string }
>;
"onct-change"?: EventHandler<
{ value: string; oldValue: string; name: string }
>;
"onct-focus"?: EventHandler<{ value: string; name: string }>;
"onct-blur"?: EventHandler<{ value: string; name: string }>;
"onct-keydown"?: EventHandler<{
key: string;
value: string;
shiftKey: boolean;
ctrlKey: boolean;
metaKey: boolean;
altKey: boolean;
name: string;
}>;
"onct-submit"?: EventHandler<{ value: string; name: string }>;
}

interface CTInputLegacyAttributes<T> extends CTHTMLAttributes<T> {
"value"?: CellLike<string>;
"placeholder"?: string;
Expand Down Expand Up @@ -3905,6 +3947,10 @@ declare global {
CTInputAttributes<CTInputElement>,
CTInputElement
>;
"ct-textarea": CTDOM.DetailedHTMLProps<
CTTextAreaAttributes<CTTextAreaElement>,
CTTextAreaElement
>;
"ct-file-input": CTDOM.DetailedHTMLProps<
CTFileInputAttributes<CTFileInputElement>,
CTFileInputElement
Expand Down
42 changes: 13 additions & 29 deletions packages/patterns/todo-list.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/// <cts-enable />
import { Cell, Default, derive, NAME, pattern, UI } from "commontools";
import Suggestion from "./suggestion.tsx";
import { Cell, Default, NAME, pattern, UI, wish } from "commontools";

interface TodoItem {
title: string;
Expand All @@ -16,13 +15,6 @@ interface Output {
}

export default pattern<Input, Output>(({ items }) => {
// AI suggestion based on current todos
const suggestion = Suggestion({
situation:
"Based on my todo list, use a pattern to help me. For sub-tasks and additional tasks, use a todo list.",
context: { items },
});

return {
[NAME]: "Todo with Suggestions",
[UI]: (
Expand All @@ -43,6 +35,12 @@ export default pattern<Input, Output>(({ items }) => {
{/* Todo items with per-item suggestions */}
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
{items.map((item) => {
// AI suggestion based on current todos
const wishResult = wish({
query: item.title,
context: { item, items },
});

return (
<div
style={{
Expand All @@ -63,7 +61,7 @@ export default pattern<Input, Output>(({ items }) => {
? { textDecoration: "line-through", opacity: 0.6 }
: {}}
>
{item.title}
<ct-textarea $value={item.title} />
</span>
</ct-checkbox>
<ct-button
Expand All @@ -80,31 +78,17 @@ export default pattern<Input, Output>(({ items }) => {
×
</ct-button>
</div>

<details open>
<summary>AI Suggestion</summary>
{wishResult}
</details>
</div>
);
})}
</div>

{/* AI Suggestion */}
<ct-cell-context $cell={suggestion} label="AI Suggestion">
<div
style={{
marginTop: "16px",
padding: "12px",
backgroundColor: "#f5f5f5",
borderRadius: "8px",
}}
>
<h3>AI Suggestion</h3>
{derive(suggestion, (s) =>
s?.result ?? (
<span style={{ opacity: 0.6 }}>Getting suggestion...</span>
))}
</div>
</ct-cell-context>
</div>
),
items,
suggestion,
};
});
2 changes: 1 addition & 1 deletion packages/runner/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class Runner implements IRunner {
// copy keys, since we'll mutate the collection while iterating
const cacheKeys = [...this.resultRecipeCache.keys()];
cacheKeys.filter((key) => key.startsWith(`${notification.space}/`))
.forEach(this.resultRecipeCache.delete);
.forEach((key) => this.resultRecipeCache.delete(key));
}
return { done: false };
},
Expand Down
13 changes: 7 additions & 6 deletions packages/shell/src/lib/app/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export function appViewToUrlPath(view: AppView): `/${string}` {
? `/${view.spaceName}/${view.charmId}`
: `/${view.spaceName}`;
} else if ("spaceDid" in view) {
// did routes not yet supported
return "charmId" in view
? `/${view.spaceDid}/${view.charmId}`
: `/${view.spaceDid}`;
Expand All @@ -59,10 +58,12 @@ export function urlToAppView(url: URL): AppView {
segments.shift(); // shift off the pathnames' prefix "/";
const [first, charmId] = [segments[0], segments[1]];

if (charmId) {
return { spaceName: first, charmId };
} else if (first) {
return { spaceName: first };
if (!first) {
return { builtin: "home" };
}
if (isDID(first)) {
return charmId ? { spaceDid: first, charmId } : { spaceDid: first };
} else {
return charmId ? { spaceName: first, charmId } : { spaceName: first };
}
return { builtin: "home" };
}
Loading
Loading