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
27 changes: 25 additions & 2 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ on:
- "hosts/macos/**"
- "tools/macos.ts"
- "apps/note/**"
- "apps/desk98/**"
- "framework/compiler/**"
- "assets/fonts/**"
- "tests/platform-contracts.test.ts"
- "tests/note.test.ts"
- "tests/desk98.test.ts"
- "tests/desk98-sim.test.ts"
- "tests/font-bake.test.ts"
- "tests/npm-package.test.ts"
- "package.json"
Expand All @@ -37,10 +40,13 @@ on:
- "hosts/macos/**"
- "tools/macos.ts"
- "apps/note/**"
- "apps/desk98/**"
- "framework/compiler/**"
- "assets/fonts/**"
- "tests/platform-contracts.test.ts"
- "tests/note.test.ts"
- "tests/desk98.test.ts"
- "tests/desk98-sim.test.ts"
- "tests/font-bake.test.ts"
- "tests/npm-package.test.ts"
- "package.json"
Expand Down Expand Up @@ -75,8 +81,8 @@ jobs:
- name: Install JS dependencies
run: bun install --frozen-lockfile

- name: Contract + note + platform + npm package tests
run: bun test tests/platform-contracts.test.ts tests/note.test.ts tests/font-bake.test.ts tests/npm-package.test.ts && bun tests/contract.ts
- name: Contract + note + desk98 + platform + npm package tests
run: bun test tests/platform-contracts.test.ts tests/note.test.ts tests/desk98.test.ts tests/font-bake.test.ts tests/npm-package.test.ts && bun tests/contract.ts

- name: Compile the note against macos-app (plan + bundle + pak)
run: |
Expand All @@ -90,6 +96,23 @@ jobs:
await Bun.write(".pocket/macos-app/note-main.plan.json", JSON.stringify(r.plan, null, 2) + "\n");'
bun tools/build.ts --plan=.pocket/macos-app/note-main.plan.json

- name: Compile desk98 against macos-app (vue-vapor, W95FA pak.json splice + SVG icons)
run: |
bun -e '
import { validateAndResolveBuildPlan } from "./framework/src/manifest/resolve.ts";
import { mkdirSync } from "node:fs";
const m = await Bun.file("apps/desk98/pocket.json").json();
const r = validateAndResolveBuildPlan(m, { target: "macos-app" });
if (!r.ok) throw new Error(JSON.stringify(r.diagnostics));
mkdirSync(".pocket/macos-app", { recursive: true });
await Bun.write(".pocket/macos-app/desk98-main.plan.json", JSON.stringify(r.plan, null, 2) + "\n");'
bun tools/build.ts --plan=.pocket/macos-app/desk98-main.plan.json

- name: desk98 sim smoke (vue-vapor bundle)
run: |
bun tools/build.ts desk98-main --framework=vue-vapor
bun test --conditions=browser tests/desk98-sim.test.ts

- name: Test core (TEXT_RUN + native measure gates)
run: cargo test --locked --manifest-path engine/core/Cargo.toml

Expand Down
31 changes: 31 additions & 0 deletions apps/desk98/AboutView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script setup lang="ts">
// About dialog content (OK button hit math in programs.ts aboutHit).
import { Image, View } from "@pocketjs/framework/vue-vapor/components";
import type { AboutData } from "./state.ts";
import T98 from "./T98.vue";
import Button98 from "./Button98.vue";

const props = defineProps<{ data: AboutData }>();
</script>

<template>
<View class="flex-1 flex-col p-[10] gap-[8]">
<View class="flex-row items-center gap-[10]">
<Image class="w-[32] h-[32]" src="icons/computer.svg" />
<T98 xl t="PocketJS 98" />
</View>
<View class="h-[2] flex-col">
<View class="h-[1] bg-[#808080]" />
<View class="h-[1] bg-[#ffffff]" />
</View>
<T98 t="A desktop compositor demo on the gpui backend." />
<T98 t="Vue Vapor templates over the same DrawList the" />
<T98 t="consoles boot; windows, menus and shortcuts" />
<T98 t="live in the guest." />
<T98 cls="text-[#808080]" t="github.com/pocket-stack/pocketjs" />
<View class="flex-1" />
<View class="flex-row justify-end">
<Button98 label="OK" :armed="props.data.armed.value === 'ok'" />
</View>
</View>
</template>
21 changes: 21 additions & 0 deletions apps/desk98/Button98.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script setup lang="ts">
// Dialog push button; armed = pressed bevel + 1px content nudge.
import { View } from "@pocketjs/framework/vue-vapor/components";
import T98 from "./T98.vue";

const props = defineProps<{ label: string; armed: boolean }>();
</script>

<template>
<View
:class="
props.armed
? 'w-[75] h-[23] flex-col justify-center items-center bg-[#c0c0c0] bevel-[#000000,#ffffff,#808080,#dfdfdf]'
: 'w-[75] h-[23] flex-col justify-center items-center bg-[#c0c0c0] bevel-[#ffffff,#000000,#dfdfdf,#808080]'
"
>
<View :class="props.armed ? 'ml-[1] mt-[1]' : ''">
<T98 :t="props.label" />
</View>
</View>
</template>
39 changes: 39 additions & 0 deletions apps/desk98/CaptionButtons.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<script setup lang="ts">
// Caption controls, flush right and flush against each other (wm.ts
// captionButtonXs mirrors this row). Press feedback inverts the bevel and
// nudges the glyph one px — app.vue owns the hit testing and drives
// win.pressedBtn off the raw pointer stream.
import { Image, View } from "@pocketjs/framework/vue-vapor/components";
import type { CaptionButton } from "./wm.ts";
import type { WinCtl } from "./state.ts";

const props = defineProps<{ win: WinCtl }>();

const BTN_ICON: Record<CaptionButton, string> = {
min: "icons/cap-min.svg",
max: "icons/cap-max.svg",
close: "icons/cap-close.svg",
};

const srcFor = (btn: CaptionButton): string =>
btn === "max" && props.win.maximized.value ? "icons/cap-restore.svg" : BTN_ICON[btn];
</script>

<template>
<View class="flex-row items-center">
<View
v-for="btn in props.win.buttons"
:key="btn"
:class="
props.win.pressedBtn.value === btn
? 'w-[16] h-[14] flex-col justify-center items-center bg-[#c0c0c0] bevel-[#000000,#ffffff,#808080,#dfdfdf]'
: 'w-[16] h-[14] flex-col justify-center items-center bg-[#c0c0c0] bevel-[#ffffff,#000000,#dfdfdf,#808080]'
"
>
<Image
:class="props.win.pressedBtn.value === btn ? 'w-[8] h-[8] ml-[1] mt-[1]' : 'w-[8] h-[8]'"
:src="srcFor(btn)"
/>
</View>
</View>
</template>
21 changes: 21 additions & 0 deletions apps/desk98/Counter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script setup lang="ts">
// Three-digit 7-seg counter (mine count / timer), clamped to -99..999.
import { View } from "@pocketjs/framework/vue-vapor/components";
import { computed } from "vue";
import Digit from "./Digit.vue";

const props = defineProps<{ value: number }>();

const text = computed(() => {
const v = Math.max(-99, Math.min(999, Math.round(props.value)));
return v < 0 ? "-" + String(-v).padStart(2, "0") : String(v).padStart(3, "0");
});
</script>

<template>
<View class="flex-row bevel-[#808080,#ffffff] p-[1] gap-0">
<Digit :ch="text[0]" />
<Digit :ch="text[1]" />
<Digit :ch="text[2]" />
</View>
</template>
23 changes: 23 additions & 0 deletions apps/desk98/DesktopIcons.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script setup lang="ts">
// Desktop icons: a left column of 32px art + label, teal behind.
import { Image, View } from "@pocketjs/framework/vue-vapor/components";
import type { DeskIcon } from "./state.ts";
import T98 from "./T98.vue";

const props = defineProps<{ icons: DeskIcon[]; selected: number }>();
</script>

<template>
<View class="absolute left-0 top-0 flex-col gap-[10] p-[8]">
<View
v-for="(icon, i) in props.icons"
:key="icon.label"
class="w-[74] flex-col items-center gap-[3]"
>
<Image class="w-[32] h-[32]" :src="icon.icon" />
<View :class="props.selected === i ? 'bg-[#000080] px-[2]' : 'px-[2]'">
<T98 cls="text-[#ffffff]" :t="icon.label" />
</View>
</View>
</View>
</template>
39 changes: 39 additions & 0 deletions apps/desk98/Digit.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<script setup lang="ts">
// One 7-seg digit, 13×23, red on black.
// a
// f b segments: bit 0..6 = a b c d e f g
// g
// e c
// d
import { View } from "@pocketjs/framework/vue-vapor/components";

const props = defineProps<{ ch: string }>();

const SEGS: Record<string, number> = {
"0": 0b0111111,
"1": 0b0000110,
"2": 0b1011011,
"3": 0b1001111,
"4": 0b1100110,
"5": 0b1101101,
"6": 0b1111101,
"7": 0b0000111,
"8": 0b1111111,
"9": 0b1101111,
"-": 0b1000000,
" ": 0,
};
const on = (bit: number): boolean => (((SEGS[props.ch] ?? 0) >> bit) & 1) === 1;
</script>

<template>
<View class="w-[13] h-[23] bg-[#000000] relative">
<View :class="on(0) ? 'absolute left-[2] top-[1] w-[9] h-[2] bg-[#ff0000]' : 'absolute left-[2] top-[1] w-[9] h-[2] bg-[#3a0000]'" />
<View :class="on(1) ? 'absolute left-[10] top-[2] w-[2] h-[9] bg-[#ff0000]' : 'absolute left-[10] top-[2] w-[2] h-[9] bg-[#3a0000]'" />
<View :class="on(2) ? 'absolute left-[10] top-[12] w-[2] h-[9] bg-[#ff0000]' : 'absolute left-[10] top-[12] w-[2] h-[9] bg-[#3a0000]'" />
<View :class="on(3) ? 'absolute left-[2] top-[20] w-[9] h-[2] bg-[#ff0000]' : 'absolute left-[2] top-[20] w-[9] h-[2] bg-[#3a0000]'" />
<View :class="on(4) ? 'absolute left-[1] top-[12] w-[2] h-[9] bg-[#ff0000]' : 'absolute left-[1] top-[12] w-[2] h-[9] bg-[#3a0000]'" />
<View :class="on(5) ? 'absolute left-[1] top-[2] w-[2] h-[9] bg-[#ff0000]' : 'absolute left-[1] top-[2] w-[2] h-[9] bg-[#3a0000]'" />
<View :class="on(6) ? 'absolute left-[2] top-[10] w-[9] h-[3] bg-[#ff0000]' : 'absolute left-[2] top-[10] w-[9] h-[3] bg-[#3a0000]'" />
</View>
</template>
57 changes: 57 additions & 0 deletions apps/desk98/FolderView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<script setup lang="ts">
// Explorer details view: column headers, icon rows, "(empty)", the status
// bar with an object count and the resize grip.
import { Image, View } from "@pocketjs/framework/vue-vapor/components";
import type { FolderData } from "./state.ts";
import T98 from "./T98.vue";

const props = defineProps<{ data: FolderData; resizable: boolean }>();
const d = props.data;
</script>

<template>
<View class="flex-1 flex-col">
<View class="flex-1 flex-col bg-[#ffffff] bevel-[#808080,#ffffff,#000000,#dfdfdf] p-[1] overflow-hidden">
<View class="h-[17] flex-row shrink-0">
<View class="flex-1 flex-row items-center px-[6] bg-[#c0c0c0] bevel-[#ffffff,#000000,#dfdfdf,#808080]">
<T98 t="Name" />
</View>
<View class="w-[64] flex-row items-center justify-end px-[6] bg-[#c0c0c0] bevel-[#ffffff,#000000,#dfdfdf,#808080]">
<T98 t="Size" />
</View>
<View class="w-[104] flex-row items-center px-[6] bg-[#c0c0c0] bevel-[#ffffff,#000000,#dfdfdf,#808080]">
<T98 t="Type" />
</View>
</View>
<View
v-for="(row, i) in d.rows"
:key="row.name"
:class="
d.selected.value === i
? 'h-[17] flex-row items-center px-[2] bg-[#000080] shrink-0'
: 'h-[17] flex-row items-center px-[2] shrink-0'
"
>
<Image class="w-[16] h-[16] mr-[4]" :src="row.icon" />
<View class="flex-1 flex-row overflow-hidden">
<T98 :cls="d.selected.value === i ? 'text-[#ffffff]' : 'text-[#000000]'" :t="row.name" />
</View>
<View class="w-[60] flex-row justify-end">
<T98 :cls="d.selected.value === i ? 'text-[#ffffff]' : 'text-[#000000]'" :t="row.size" />
</View>
<View class="w-[100] flex-row pl-[6]">
<T98 :cls="d.selected.value === i ? 'text-[#ffffff]' : 'text-[#000000]'" :t="row.type" />
</View>
</View>
<View v-if="d.rows.length === 0" class="flex-1 flex-col justify-center items-center">
<T98 cls="text-[#808080]" t="(empty)" />
</View>
</View>
<View class="h-[20] flex-row items-end gap-[2] pt-[2]">
<View class="flex-1 h-[18] flex-row items-center px-[6] bevel-[#808080,#ffffff]">
<T98 :t="`${d.rows.length} object(s)`" />
</View>
<Image v-if="props.resizable" class="w-[16] h-[16]" src="icons/grip.svg" />
</View>
</View>
</template>
55 changes: 55 additions & 0 deletions apps/desk98/MinesCell.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<script setup lang="ts">
// One field cell: raised while hidden, flat when revealed (red on the bust
// mine), flag/mine art, colored adjacency digit.
import { Image, View } from "@pocketjs/framework/vue-vapor/components";
import type { MinesData } from "./state.ts";
import type { Cell } from "./mines.ts";
import T98 from "./T98.vue";

const props = defineProps<{ data: MinesData; i: number }>();

const c = (): Cell => props.data.board.value.cells[props.i];
const bust = (): boolean => props.data.board.value.bust === props.i;
const heldDown = (): boolean => props.data.held.value === props.i && c().state === "hidden";

const NUM_COLORS = [
"", // 0 unused
"text-[#0000ff]",
"text-[#008000]",
"text-[#ff0000]",
"text-[#000080]",
"text-[#800000]",
"text-[#008080]",
"text-[#000000]",
"text-[#808080]",
];
</script>

<template>
<View
v-if="c().state !== 'revealed'"
:class="
heldDown()
? 'w-[16] h-[16] bg-[#c0c0c0] bevel-[#808080,#c0c0c0] flex-col justify-center items-center'
: 'w-[16] h-[16] bg-[#c0c0c0] bevel-[#ffffff,#808080] bevel-w-[2] flex-col justify-center items-center'
"
>
<Image v-if="c().state === 'flag'" class="w-[8] h-[8]" src="icons/flag.svg" />
</View>
<View
v-else
:class="
bust()
? 'w-[16] h-[16] bg-[#ff0000] bevel-[#808080,#ff0000] flex-col justify-center items-center'
: 'w-[16] h-[16] bg-[#c0c0c0] bevel-[#808080,#c0c0c0] flex-col justify-center items-center'
"
>
<Image v-if="c().mine" class="w-[8] h-[8]" src="icons/mine.svg" />
<T98
v-else-if="c().adj > 0"
bold
:cls="NUM_COLORS[c().adj] || 'text-[#000000]'"
:t="String(c().adj)"
/>
</View>
</template>
Loading