-
-
Notifications
You must be signed in to change notification settings - Fork 22
Promote dev to master #979
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 41 commits
9dd6c6f
5e121df
7e03e51
8b49198
cb28d78
e9031f2
70ec49f
7cc51ea
8cd3554
18a15f2
e820823
3b63f84
1804c67
b335b42
ea95493
65d4fca
2444cf0
9d8fda6
7fc1453
02d468a
ac9785a
9f705df
659e3bd
035b017
2f765b7
0d76944
46a1c6d
02941a6
9e7a028
a2f44b2
8349f40
2b429ed
d6ec6d3
b96efcb
0f0db09
f212500
9f27e0d
e51b2fa
efd8994
6243ab9
fa09f7e
e85f64a
9bfd008
b961e29
4111d12
8086528
57dfde0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| /Volumes/NVMe/Users/jay/Development/tinyagentos/desktop/node_modules | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CRITICAL: Remove the host-specific symlink and gitignore node_modules instead. This symlink points to an absolute macOS-specific path ( Action required:
🤖 Prompt for AI Agents |
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import { describe, it, expect } from "vitest"; | ||
| import { render, screen, fireEvent } from "@testing-library/react"; | ||
| import React from "react"; | ||
| import { AppStudioApp } from "./AppStudioApp"; | ||
|
|
||
| describe("AppStudioApp", () => { | ||
| it("renders the titlebar with App Studio", () => { | ||
| render(<AppStudioApp windowId="test" />); | ||
| expect(screen.getByText("App Studio")).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("renders all rail items", () => { | ||
| const { container } = render(<AppStudioApp windowId="test" />); | ||
| const nav = container.querySelector("nav[aria-label='App Studio views']"); | ||
| expect(nav).toBeInTheDocument(); | ||
| // Rail buttons are inside the nav | ||
| const railBtns = nav!.querySelectorAll("button"); | ||
| const labels = Array.from(railBtns).map((b) => b.getAttribute("aria-label")); | ||
| expect(labels).toContain("Build"); | ||
| expect(labels).toContain("Templates"); | ||
| expect(labels).toContain("Publish"); | ||
| expect(labels).toContain("SDK"); | ||
| }); | ||
|
|
||
| it("shows Build view by default", () => { | ||
| render(<AppStudioApp windowId="test" />); | ||
| expect(screen.getByRole("heading", { name: /^build$/i })).toBeInTheDocument(); | ||
| // checkerboard sandbox area has the live preview header text | ||
| expect(screen.getByText("Build log")).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("switches to Templates view and shows template cards", () => { | ||
| const { container } = render(<AppStudioApp windowId="test" />); | ||
| const nav = container.querySelector("nav[aria-label='App Studio views']")!; | ||
| const templatesBtn = Array.from(nav.querySelectorAll("button")).find( | ||
| (b) => b.getAttribute("aria-label") === "Templates" | ||
| )!; | ||
| fireEvent.click(templatesBtn); | ||
| // hero heading | ||
| expect(screen.getByText(/build a taOS app in plain words/i)).toBeInTheDocument(); | ||
| // template card labels | ||
| expect(screen.getByText("Dashboard")).toBeInTheDocument(); | ||
| expect(screen.getByText("Tracker")).toBeInTheDocument(); | ||
| expect(screen.getByText("Kanban")).toBeInTheDocument(); | ||
| expect(screen.getByText("Blank")).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it("switches to Publish view and shows capability rows", () => { | ||
| const { container } = render(<AppStudioApp windowId="test" />); | ||
| const nav = container.querySelector("nav[aria-label='App Studio views']")!; | ||
| const publishBtn = Array.from(nav.querySelectorAll("button")).find( | ||
| (b) => b.getAttribute("aria-label") === "Publish" | ||
| )!; | ||
| fireEvent.click(publishBtn); | ||
| // app identity | ||
| expect(screen.getAllByText("Chore Quest").length).toBeGreaterThan(0); | ||
| // capability row labels | ||
| expect(screen.getByTestId("perm-row-workspace")).toBeInTheDocument(); | ||
| expect(screen.getByTestId("perm-row-notifications")).toBeInTheDocument(); | ||
| expect(screen.getByTestId("perm-row-household")).toBeInTheDocument(); | ||
| // publish button | ||
| expect(screen.getByRole("button", { name: /publish to my store/i })).toBeInTheDocument(); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| import { useState } from "react"; | ||
| import { Sparkles, LayoutGrid, Share2, CircleDot } from "lucide-react"; | ||
| import { BuildView } from "./appstudio/BuildView"; | ||
| import { TemplatesView } from "./appstudio/TemplatesView"; | ||
| import { PublishView } from "./appstudio/PublishView"; | ||
|
|
||
| /* ------------------------------------------------------------------ */ | ||
| /* App Studio -- shell */ | ||
| /* */ | ||
| /* Build taOS apps from plain words. An agent generates them against */ | ||
| /* the taOS SDK, sandboxed and safe. Publish to your Store or share */ | ||
| /* with family. */ | ||
| /* */ | ||
| /* Shell follows the canonical studio pattern from GameStudioApp: */ | ||
| /* 46px centered titlebar, 68px icon rail, per-view subfolder. */ | ||
| /* ------------------------------------------------------------------ */ | ||
|
|
||
| type AppStudioView = "build" | "templates" | "publish" | "sdk"; | ||
|
|
||
| const RAIL: { id: AppStudioView; label: string; icon: typeof Sparkles }[] = [ | ||
| { id: "build", label: "Build", icon: Sparkles }, | ||
| { id: "templates", label: "Templates", icon: LayoutGrid }, | ||
| { id: "publish", label: "Publish", icon: Share2 }, | ||
| ]; | ||
|
|
||
| const RAIL_BOTTOM: { id: AppStudioView; label: string; icon: typeof Sparkles }[] = [ | ||
| { id: "sdk", label: "SDK", icon: CircleDot }, | ||
| ]; | ||
|
|
||
| export function AppStudioApp({ windowId: _windowId }: { windowId: string }) { | ||
| const [view, setView] = useState<AppStudioView>("build"); | ||
|
|
||
| function RailButton({ id, label, icon: Icon }: { id: AppStudioView; label: string; icon: typeof Sparkles }) { | ||
| const on = view === id; | ||
| return ( | ||
| <button | ||
| key={id} | ||
| type="button" | ||
| aria-label={label} | ||
| aria-current={on ? "page" : undefined} | ||
| onClick={() => setView(id)} | ||
| className={`flex h-[46px] w-[46px] flex-col items-center justify-center gap-0.5 rounded-xl text-[9px] font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ${ | ||
| on | ||
| ? "bg-gradient-to-b from-accent/25 to-transparent text-accent" | ||
| : "text-shell-text-tertiary hover:bg-white/10 hover:text-shell-text-secondary" | ||
| }`} | ||
| > | ||
| <Icon size={21} /> | ||
| {label} | ||
| </button> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <div className="flex h-full min-h-0 flex-col overflow-hidden bg-shell-bg text-shell-text select-none"> | ||
| {/* titlebar */} | ||
| <div className="flex h-[46px] flex-none items-center justify-center border-b border-shell-border"> | ||
| <span className="text-[13px] font-semibold tracking-[-0.01em]">App Studio</span> | ||
| </div> | ||
|
|
||
| <div className="flex min-h-0 flex-1"> | ||
| {/* left rail */} | ||
| <nav | ||
| aria-label="App Studio views" | ||
| className="flex w-[68px] flex-none flex-col items-center gap-1.5 border-r border-shell-border bg-shell-bg-deep py-3.5" | ||
| > | ||
| {RAIL.map((r) => ( | ||
| <RailButton key={r.id} {...r} /> | ||
| ))} | ||
| <div className="flex-1" /> | ||
| {RAIL_BOTTOM.map((r) => ( | ||
| <RailButton key={r.id} {...r} /> | ||
| ))} | ||
| </nav> | ||
|
|
||
| {/* active surface */} | ||
| <div className="flex min-w-0 flex-1 flex-col"> | ||
| {view === "build" && <BuildView />} | ||
| {view === "templates" && <TemplatesView />} | ||
| {view === "publish" && <PublishView />} | ||
| {view === "sdk" && ( | ||
| <div className="flex flex-1 items-center justify-center text-[13px] text-shell-text-tertiary"> | ||
| SDK docs coming soon | ||
| </div> | ||
| )} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import { render, screen, fireEvent } from "@testing-library/react"; | ||
| import { describe, it, expect } from "vitest"; | ||
| import { CodingStudioApp } from "./CodingStudioApp"; | ||
|
|
||
| function renderApp() { | ||
| return render(<CodingStudioApp windowId="test-window" />); | ||
| } | ||
|
|
||
| describe("CodingStudioApp", () => { | ||
| it("renders the app titlebar", () => { | ||
| renderApp(); | ||
| expect(screen.getByText("Coding Studio")).toBeDefined(); | ||
| }); | ||
|
|
||
| it("renders all rail items", () => { | ||
| renderApp(); | ||
| // Rail buttons use aria-label for exact matching via the nav element | ||
| const nav = screen.getByRole("navigation", { name: "Coding Studio views" }); | ||
| expect(nav).toBeDefined(); | ||
| expect(screen.getByRole("button", { name: "Code" })).toBeDefined(); | ||
| expect(screen.getByRole("button", { name: "Preview" })).toBeDefined(); | ||
| expect(screen.getByRole("button", { name: "Templates" })).toBeDefined(); | ||
| expect(screen.getByRole("button", { name: "Models" })).toBeDefined(); | ||
| }); | ||
|
|
||
| it("shows Build view by default with Build rail item active", () => { | ||
| renderApp(); | ||
| // The rail Build button (inside nav) should be aria-current="page" | ||
| const nav = screen.getByRole("navigation", { name: "Coding Studio views" }); | ||
| const railBuildBtn = nav.querySelector('[aria-label="Build"]') as HTMLElement; | ||
| expect(railBuildBtn).toBeTruthy(); | ||
| expect(railBuildBtn.getAttribute("aria-current")).toBe("page"); | ||
| }); | ||
|
|
||
| it("switches to Templates view on rail click", () => { | ||
| renderApp(); | ||
| fireEvent.click(screen.getByRole("button", { name: "Templates" })); | ||
| expect(screen.getByRole("button", { name: "Templates" }).getAttribute("aria-current")).toBe( | ||
| "page", | ||
| ); | ||
| expect(screen.getByText("Describe what you want to build.")).toBeDefined(); | ||
| }); | ||
|
|
||
| it("Templates view shows all 8 template cards", () => { | ||
| renderApp(); | ||
| fireEvent.click(screen.getByRole("button", { name: "Templates" })); | ||
| const expectedNames = [ | ||
| "Web App", | ||
| "REST API", | ||
| "CLI Tool", | ||
| "Discord Bot", | ||
| "Static Site", | ||
| "Data Pipeline", | ||
| "Python Script", | ||
| "Browser Extension", | ||
| ]; | ||
| for (const name of expectedNames) { | ||
| expect(screen.getByText(name)).toBeDefined(); | ||
| } | ||
| }); | ||
|
|
||
| it("switches to Preview view on rail click and shows preview header", () => { | ||
| renderApp(); | ||
| fireEvent.click(screen.getByRole("button", { name: "Preview" })); | ||
| expect(screen.getByRole("button", { name: "Preview" }).getAttribute("aria-current")).toBe( | ||
| "page", | ||
| ); | ||
| // Preview header h2 | ||
| expect(screen.getByRole("heading", { name: "Preview" })).toBeDefined(); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| import { useState } from "react"; | ||
| import { Sparkles, Code2, Play, LayoutGrid, Settings2 } from "lucide-react"; | ||
| import { BuildView } from "./codingstudio/BuildView"; | ||
| import { TemplatesView } from "./codingstudio/TemplatesView"; | ||
| import { PreviewView } from "./codingstudio/PreviewView"; | ||
|
|
||
| type CodingView = "build" | "code" | "preview" | "templates"; | ||
|
|
||
| const RAIL: { id: CodingView; label: string; icon: typeof Sparkles }[] = [ | ||
| { id: "build", label: "Build", icon: Sparkles }, | ||
| { id: "code", label: "Code", icon: Code2 }, | ||
| { id: "preview", label: "Preview", icon: Play }, | ||
| { id: "templates", label: "Templates", icon: LayoutGrid }, | ||
| ]; | ||
|
|
||
| export function CodingStudioApp({ windowId: _windowId }: { windowId: string }) { | ||
| const [view, setView] = useState<CodingView>("build"); | ||
|
|
||
| return ( | ||
| <div className="flex h-full min-h-0 flex-col overflow-hidden bg-shell-bg text-shell-text select-none"> | ||
| {/* title strip */} | ||
| <div className="flex h-[46px] flex-none items-center justify-center border-b border-shell-border"> | ||
| <span className="text-[13px] font-semibold tracking-[-0.01em]">Coding Studio</span> | ||
| </div> | ||
|
|
||
| <div className="flex min-h-0 flex-1"> | ||
| {/* left rail */} | ||
| <nav | ||
| aria-label="Coding Studio views" | ||
| className="flex w-[68px] flex-none flex-col items-center gap-1.5 border-r border-shell-border bg-shell-bg-deep py-3.5" | ||
| > | ||
| {RAIL.map((r) => { | ||
| const Icon = r.icon; | ||
| const on = view === r.id; | ||
| return ( | ||
| <button | ||
| key={r.id} | ||
| type="button" | ||
| aria-label={r.label} | ||
| aria-current={on ? "page" : undefined} | ||
| onClick={() => setView(r.id)} | ||
| className={`flex h-[46px] w-[46px] flex-col items-center justify-center gap-0.5 rounded-xl text-[9px] font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ${ | ||
| on | ||
| ? "bg-gradient-to-b from-accent/25 to-transparent text-accent" | ||
| : "text-shell-text-tertiary hover:bg-white/10 hover:text-shell-text-secondary" | ||
| }`} | ||
| > | ||
| <Icon size={21} /> | ||
| {r.label} | ||
| </button> | ||
| ); | ||
| })} | ||
| <div className="flex-1" /> | ||
| <button | ||
| type="button" | ||
| aria-label="Models" | ||
| className="flex h-[46px] w-[46px] flex-col items-center justify-center gap-0.5 rounded-xl text-[9px] font-semibold text-shell-text-tertiary transition-colors hover:bg-white/10 hover:text-shell-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40" | ||
| > | ||
| <Settings2 size={21} /> | ||
| Models | ||
| </button> | ||
| </nav> | ||
|
|
||
| {/* active surface */} | ||
| <div className="flex min-w-0 flex-1 flex-col"> | ||
| {view === "build" && <BuildView />} | ||
| {view === "code" && <BuildView />} | ||
| {view === "preview" && <PreviewView />} | ||
| {view === "templates" && <TemplatesView />} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an absolute symlink to
/Volumes/NVMe/User. Committing host-specific symlinks will break checkouts on other machines and can leak local environment assumptions. Remove the symlink and addnode_modulesto.gitignoreinstead.Reply with
@kilocode-bot fix itto have Kilo Code address this issue.