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
20 changes: 20 additions & 0 deletions ___tests___/jamlDefault.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, expect, it } from "vitest";
import { Severity, getDiagnostics } from "jaml-lang";
import { DEFAULT_JAML } from "../src/modules/state/jamlSearchContext";

const errors = (jaml: string) => getDiagnostics(jaml).filter((d) => d.severity === Severity.Error);

describe("DEFAULT_JAML", () => {
it("has no diagnostics", () => {
expect(errors(DEFAULT_JAML)).toEqual([]);
});

it("declares nine should clauses", () => {
expect(DEFAULT_JAML.split("should:")[1].match(/^ - /gm)).toHaveLength(9);
});

it("the validator rejects the keys that crashed motely-wasm", () => {
const broken = DEFAULT_JAML.replace("spectralCard:", "spectral:").replace("joker: Any", "mixedJoker: Any");
expect(errors(broken).map((d) => d.message)).toEqual(["Unknown key 'spectral'.", "Unknown key 'mixedJoker'."]);
});
});
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@tanstack/react-query": "^5.85.9",
"embla-carousel-react": "^8.6.0",
"immer": "^10.1.3",
"jaml-lang": "^3.14.2",
"jaml-ui": "^4.6.0",
"js-convert-case": "^4.2.0",
"js-yaml": "^4.1.1",
Expand Down
13 changes: 9 additions & 4 deletions src/components/SeedQueue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,19 @@ export function SeedQueueHotkeys() {
const seeds = useCardStore(state => state.seedQueue.seeds);
const index = useCardStore(state => state.seedQueue.index);
const engineState = useCardStore(state => state.engineState);
const viewMode = useCardStore(state => state.applicationState.viewMode);
const stepSeedQueue = useCardStore(state => state.stepSeedQueue);
const jumpSeedQueue = useCardStore(state => state.jumpSeedQueue);

const guarded = (fn: () => void) => () => {
if (viewMode !== 'jaml') fn();
};

useHotkeys([
["ArrowRight", () => stepSeedQueue(1)],
["ArrowLeft", () => stepSeedQueue(-1)],
["Home", () => jumpSeedQueue(0)],
["End", () => jumpSeedQueue(seeds.length - 1)],
["ArrowRight", guarded(() => stepSeedQueue(1))],
["ArrowLeft", guarded(() => stepSeedQueue(-1))],
["Home", guarded(() => jumpSeedQueue(0))],
["End", guarded(() => jumpSeedQueue(seeds.length - 1))],
]);

useEffect(() => {
Expand Down
15 changes: 13 additions & 2 deletions src/components/blueprint/jamlView/JamlView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
parseJaml,
} from "jaml-ui";
import { JamlAesthetic, MotelyDeck, MotelyStake } from "motely-wasm";
import { Severity, getDiagnostics } from "jaml-lang";
import { useJamlSearch } from "../../../modules/state/jamlSearchContext.tsx";
import type { JamlIdeSearchResult, JimboStatus } from "jaml-ui";
import type { MotelySeedScore } from "motely-wasm";
Expand Down Expand Up @@ -164,7 +165,15 @@ export default function JamlView() {
}
}, [jamlText]);

const jamlErrors = useMemo(
() => getDiagnostics(jamlText)
.filter((d) => d.severity === Severity.Error)
.map((d) => `line ${d.range.start.line + 1}: ${d.message}`),
[jamlText],
);

const isSearching = status === "running" || status === "booting";
const startBlocked = !isSearching && jamlErrors.length > 0;

const estimate = useMemo(() => {
try {
Expand Down Expand Up @@ -252,10 +261,10 @@ export default function JamlView() {
const handleSearch = useCallback(() => {
if (isSearching) {
workerRef.current?.postMessage({ type: "cancel" } satisfies WorkerRequest);
} else {
} else if (jamlErrors.length === 0) {
runSearch();
}
}, [isSearching, runSearch]);
}, [isSearching, jamlErrors, runSearch]);

const ideResults: Array<JamlIdeSearchResult> = useMemo(
() =>
Expand Down Expand Up @@ -326,6 +335,7 @@ export default function JamlView() {
? ` · ${Math.round(stats.percentComplete)}%`
: ""}
</JimboText>
{jamlErrors.map((line) => <JimboText key={line} size="sm" tone="red">{line}</JimboText>)}
{error && <JimboText size="sm" tone="red">{error}</JimboText>}
</JimboStack>
</JimboPanel>
Expand Down Expand Up @@ -537,6 +547,7 @@ export default function JamlView() {
<JimboButton
fullWidth
tone={isSearching ? "red" : "blue"}
disabled={startBlocked}
onClick={handleSearch}
>
{isSearching ? "Cancel" : "Start Search"}
Expand Down
18 changes: 14 additions & 4 deletions src/components/blueprint/layout/header.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from "react";
import {useViewportSize} from "@mantine/hooks";
import {ActionIcon, AppShell, Box, Burger, Button, Center, Container, CopyButton, Group, Title, Tooltip} from "@mantine/core";
import {ActionIcon, AppShell, Box, Burger, Button, Center, Container, CopyButton, Group, SegmentedControl, Title, Tooltip} from "@mantine/core";
import {IconMessageChatbot} from "@tabler/icons-react";
import {SeedQueueControls} from "../../SeedQueue.tsx";
import {useCardStore} from "../../../modules/state/store.ts";
import SearchSeedInput from "../../searchInput.tsx";
import {GaEvent} from "../../../modules/useGA.ts";
import {viewModeOptions} from "./navbar.tsx";

export default function Header() {
const { width } = useViewportSize();
Expand All @@ -17,24 +17,34 @@ export default function Header() {
const toggleOutput = useCardStore(state => state.toggleOutput);
const assistantOpened = useCardStore(state => state.applicationState.assistantOpen);
const toggleAssistant = useCardStore(state => state.toggleAssistant);
const viewMode = useCardStore(state => state.applicationState.viewMode);
const setViewMode = useCardStore(state => state.setViewMode);

return (
<AppShell.Header>
<Container fluid h={'100%'}>
<Group h={'100%'} justify={'space-between'}>
<Group flex={1}>
<Burger opened={settingsOpened} onClick={toggleSettings} hiddenFrom={'md'} size="sm" />
{viewMode !== 'jaml' && <Burger opened={settingsOpened} onClick={toggleSettings} hiddenFrom={'md'} size="sm" />}
<Center h={'100%'}>
<Group grow>
<Box flex={1}>
<Title > Blueprint </Title>
</Box>
</Group>
</Center>
{viewMode === 'jaml' && (
<SegmentedControl
size="xs"
value={viewMode}
onChange={(value: string) => setViewMode(value)}
data={viewModeOptions}
/>
)}
</Group>

<Group align={'center'}>
{width > 900 && <SeedQueueControls />}
{viewMode !== 'jaml' && width > 600 && <SeedQueueControls />}
{width > 600 && start && <Box id="search-input-header"><SearchSeedInput /></Box>}
{width > 700 && start && (
<CopyButton value={new URL(window.location.href).toString()}>
Expand Down
72 changes: 37 additions & 35 deletions src/components/blueprint/layout/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,42 @@ import { SeedQueuePanel } from "../../SeedQueue.tsx";
import { DrawSimulatorModal } from "../../DrawSimulatorModal.tsx";
import { RerollCalculatorModal } from "../../RerollCalculatorModal.tsx";

export const viewModeOptions = [
{
value: 'blueprint',
label: (
<Group gap="xs" wrap="nowrap">
<IconLayout size={16} />
<Text>Blueprint</Text>
</Group>
)
},
{
value: 'jaml',
label: (
<Group gap={4} wrap="nowrap" align="center">
<Image
src={`${import.meta.env.BASE_URL}images/JAML.ico`}
alt="JAML"
w={18}
h={18}
fit="contain"
/>
<Text size="sm" style={{ whiteSpace: 'nowrap' }}>JAML</Text>
</Group>
)
},
{
value: 'bp-settings',
label: (
<Group gap={4} wrap="nowrap" align="center">
<IconSettings size={12} />
<Text size="sm" style={{ whiteSpace: 'nowrap' }}>Settings</Text>
</Group>
)
}
];

export default function Navbar() {
const theme = useMantineTheme();
const viewMode = useCardStore(state => state.applicationState.viewMode);
Expand Down Expand Up @@ -83,41 +119,7 @@ export default function Navbar() {
fullWidth
value={viewMode}
onChange={(value: string) => setViewMode(value)}
data={[
{
value: 'blueprint',
label: (
<Group gap="xs">
<IconLayout size={16} />
<Text>Blueprint</Text>
</Group>
)
},
{
value: 'jaml',
label: (
<Group gap={4} wrap="nowrap" align="center">
<Image
src={`${import.meta.env.BASE_URL}images/JAML.ico`}
alt="JAML"
w={18}
h={18}
fit="contain"
/>
<Text size="sm" style={{ whiteSpace: 'nowrap' }}>JAML</Text>
</Group>
)
},
{
value: 'bp-settings',
label: (
<Group gap={4} wrap="nowrap" align="center">
<IconSettings size={12} />
<Text size="sm" style={{ whiteSpace: 'nowrap' }}>Settings</Text>
</Group>
)
}
]}
data={viewModeOptions}
mb="sm"
/>
<Divider mb='md' />
Expand Down
11 changes: 7 additions & 4 deletions src/components/blueprint/standardView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,7 @@ export function Blueprint() {
const { width } = useViewportSize();
const settingsOpened = useCardStore(state => state.applicationState.settingsOpen);
const outputOpened = useCardStore(state => state.applicationState.asideOpen);
const viewMode = useCardStore(state => state.applicationState.viewMode);
const download = useDownloadSeedResults()
useEffect(() => {
if(typeof window !== 'undefined' && !!download) {
Expand All @@ -579,10 +580,12 @@ export function Blueprint() {
navbar={{
width: { base: '100%', md: 400, lg: 400 },
breakpoint: 'sm',
collapsed: {
desktop: !(width > 1000) && !settingsOpened,
mobile: !settingsOpened
},
collapsed: viewMode === 'jaml'
? { desktop: true, mobile: true }
: {
desktop: !(width > 1000) && !settingsOpened,
mobile: !settingsOpened
},
}}
padding="md"
>
Expand Down
6 changes: 3 additions & 3 deletions src/modules/state/jamlSearchContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface JamlSearchContextType extends JamlSearchState {

const JamlSearchContext = createContext<JamlSearchContextType | undefined>(undefined);

const DEFAULT_JAML = `name: Blueprint Copy Engine
export const DEFAULT_JAML = `name: Blueprint Copy Engine
author: jammy
description: Blueprint rare joker with Brainstorm for joker copying synergy
deck: Red
Expand All @@ -44,9 +44,9 @@ should:
score: 50
- uncommonJoker: Showman
score: 35
- spectral: Hex
- spectralCard: Hex
score: 35
- mixedJoker: Any
- joker: Any
edition: Negative
score: 40
- tag: NegativeTag
Expand Down
3 changes: 0 additions & 3 deletions src/modules/state/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,9 +497,6 @@ export const useCardStore = create<CardStore>()(
set((prev) => {
prev.seedQueue.index = clamped;
prev.applicationState.start = true;
prev.applicationState.viewMode = prev.applicationState.viewMode === 'jaml'
? 'blueprint'
: prev.applicationState.viewMode;
saveSeedQueue(prev.seedQueue);
}, undefined, 'SeedQueue/Jump');
},
Expand Down
2 changes: 2 additions & 0 deletions src/modules/webmcp/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ async function publish() {
controller = new AbortController();
const { signal } = controller;
for (const tool of listTools()) {
if (signal.aborted) return;
try {
await ctx.registerTool(
{
Expand All @@ -46,6 +47,7 @@ async function publish() {
{ signal },
);
} catch (error) {
if (signal.aborted) return;
console.warn(`WebMCP: could not register ${tool.name}`, error);
}
}
Expand Down
10 changes: 8 additions & 2 deletions src/modules/webmcp/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,14 @@ export function registerTool(tool: BlueprintTool): () => void {
}

export function registerTools(list: Array<BlueprintTool>): () => void {
const unregister = list.map(registerTool);
return () => unregister.forEach((fn) => fn());
for (const tool of list) tools.set(tool.name, tool);
notify();
return () => {
for (const tool of list) {
if (tools.get(tool.name) === tool) tools.delete(tool.name);
}
notify();
};
}

export function listTools(): Array<BlueprintTool> {
Expand Down
Loading