Преглед изворни кода

style(studio): fix spacing — generous padding, centered content, breathing room

Sidebar: 200px→220px, generous logo area (pt-7 pb-6), wider item padding
Main area: max-w-4xl→max-w-3xl, px-6→px-10, py-8→py-12
Empty state: min-h-[60vh] vertical centering, larger ornament spacing
Chat bar: centered to max-w-3xl matching content, py-2.5→py-3.5
Header strip: px-4→px-8, h-11→h-12
Book cards: px-5→px-6, py-4→py-5
All borders softened to /20-/30 opacity for subtlety
Ma пре 7 месеци
родитељ
комит
5e465b6326

+ 0 - 1
.superpowers/brainstorm/33312-1773817943/.server-info

@@ -1 +0,0 @@
-{"type":"server-started","port":60485,"host":"127.0.0.1","url_host":"localhost","url":"http://localhost:60485","screen_dir":"/Users/majunxian/Desktop/PyProject/inkos-studio/.superpowers/brainstorm/33312-1773817943"}

+ 1 - 0
.superpowers/brainstorm/33312-1773817943/.server-stopped

@@ -0,0 +1 @@
+{"reason":"owner process exited","timestamp":1773820583908}

+ 1 - 0
.superpowers/brainstorm/33312-1773817943/.server.log

@@ -1,3 +1,4 @@
 {"type":"server-started","port":60485,"host":"127.0.0.1","url_host":"localhost","url":"http://localhost:60485","screen_dir":"/Users/majunxian/Desktop/PyProject/inkos-studio/.superpowers/brainstorm/33312-1773817943"}
 {"type":"screen-added","file":"/Users/majunxian/Desktop/PyProject/inkos-studio/.superpowers/brainstorm/33312-1773817943/layout-concept.html"}
 {"type":"screen-added","file":"/Users/majunxian/Desktop/PyProject/inkos-studio/.superpowers/brainstorm/33312-1773817943/layout-refined.html"}
+{"type":"server-stopped","reason":"owner process exited"}

+ 9 - 16
packages/studio/src/App.tsx

@@ -41,12 +41,9 @@ export function App() {
     document.documentElement.classList.toggle("dark", isDark);
   }, [isDark]);
 
-  // Check if language has been set
   useEffect(() => {
     if (project) {
-      setLanguageSet(!!project.language && project.language !== "zh" ? true : true);
-      // For now, always consider language "set" — the selector is for first-time experience
-      // In a real implementation, we'd check a "hasCompletedSetup" flag
+      setLanguageSet(true);
     }
   }, [project]);
 
@@ -63,42 +60,38 @@ export function App() {
     toLogs: () => setRoute({ page: "logs" }),
   };
 
-  // Derive active page for sidebar highlighting
   const activePage =
     route.page === "book" || route.page === "chapter" || route.page === "truth" || route.page === "analytics"
       ? `book:${(route as { bookId: string }).bookId}`
       : route.page;
 
-  // Language selector for first-time setup
   if (languageSet === null) {
-    return <div className="min-h-screen bg-background" />; // Loading
+    return <div className="min-h-screen bg-background" />;
   }
 
   return (
     <div className="h-screen bg-background text-foreground flex overflow-hidden">
-      {/* Sidebar */}
       <Sidebar nav={nav} activePage={activePage} t={t} />
 
-      {/* Main area + Chat bar */}
       <div className="flex-1 flex flex-col min-w-0">
-        {/* Header strip */}
-        <div className="h-11 shrink-0 border-b border-border/30 flex items-center justify-end px-4 gap-3">
+        {/* Thin utility strip — pushed inward */}
+        <div className="h-12 shrink-0 border-b border-border/20 flex items-center justify-end px-8 gap-4">
           <button
             onClick={() => setTheme(isDark ? "light" : "dark")}
-            className="w-7 h-7 flex items-center justify-center rounded-full bg-secondary/50 text-muted-foreground hover:text-foreground transition-colors text-xs"
+            className="w-7 h-7 flex items-center justify-center rounded-full bg-secondary/40 text-muted-foreground/60 hover:text-foreground hover:bg-secondary transition-all text-xs"
           >
             {isDark ? "☀" : "☽"}
           </button>
           {sse.connected && (
-            <span className="text-[10px] text-muted-foreground/40 uppercase tracking-wider">
+            <span className="text-[10px] text-muted-foreground/30 uppercase tracking-[0.15em]">
               {t("nav.connected")}
             </span>
           )}
         </div>
 
-        {/* Scrollable main content */}
+        {/* Scrollable main — centered with generous inset */}
         <div className="flex-1 overflow-y-auto">
-          <div className="max-w-4xl mx-auto px-6 py-8">
+          <div className="max-w-3xl mx-auto px-10 py-12">
             {route.page === "dashboard" && <Dashboard nav={nav} sse={sse} theme={theme} t={t} />}
             {route.page === "book" && <BookDetail bookId={route.bookId} nav={nav} theme={theme} t={t} />}
             {route.page === "book-create" && <BookCreate nav={nav} theme={theme} t={t} />}
@@ -111,7 +104,7 @@ export function App() {
           </div>
         </div>
 
-        {/* Chat bar - always at bottom */}
+        {/* Chat bar — inset to match content width */}
         <ChatBar t={t} sse={sse} />
       </div>
     </div>

+ 33 - 31
packages/studio/src/components/ChatBar.tsx

@@ -118,38 +118,40 @@ export function ChatBar({ t, sse }: {
     : "Tell InkOS what to do...";
 
   return (
-    <div className="border-t border-border/40 bg-card/80 backdrop-blur-sm">
+    <div className="border-t border-border/20 bg-card/60 backdrop-blur-md">
       {/* Expanded message area */}
       {expanded && messages.length > 0 && (
-        <div
-          ref={scrollRef}
-          className="max-h-[200px] overflow-y-auto px-4 py-3 space-y-2 border-b border-border/20"
-        >
-          {messages.map((msg, i) => (
-            <div
-              key={i}
-              className={`text-[13px] leading-relaxed ${
-                msg.role === "user"
-                  ? "text-foreground"
-                  : msg.content.startsWith("✗")
-                    ? "text-destructive"
-                    : msg.content.startsWith("⋯")
-                      ? "text-muted-foreground"
-                      : "text-primary"
-              }`}
-            >
-              {msg.role === "user" && <span className="text-muted-foreground/50 mr-1.5">›</span>}
-              {msg.content}
-            </div>
-          ))}
-          {loading && !messages.some((m) => m.content.startsWith("⋯")) && (
-            <div className="text-[13px] text-muted-foreground animate-pulse">⋯</div>
-          )}
+        <div className="max-w-3xl mx-auto">
+          <div
+            ref={scrollRef}
+            className="max-h-[200px] overflow-y-auto px-10 py-4 space-y-2.5"
+          >
+            {messages.map((msg, i) => (
+              <div
+                key={i}
+                className={`text-[13px] leading-relaxed ${
+                  msg.role === "user"
+                    ? "text-foreground"
+                    : msg.content.startsWith("✗")
+                      ? "text-destructive"
+                      : msg.content.startsWith("⋯")
+                        ? "text-muted-foreground"
+                        : "text-primary"
+                }`}
+              >
+                {msg.role === "user" && <span className="text-muted-foreground/40 mr-2">›</span>}
+                {msg.content}
+              </div>
+            ))}
+            {loading && !messages.some((m) => m.content.startsWith("⋯")) && (
+              <div className="text-[13px] text-muted-foreground animate-pulse">⋯</div>
+            )}
+          </div>
         </div>
       )}
 
-      {/* Input bar */}
-      <div className="px-4 py-2.5 flex items-center gap-3">
+      {/* Input bar — centered to match main content */}
+      <div className="max-w-3xl mx-auto px-10 py-3.5 flex items-center gap-4">
         <div className="flex-1 relative">
           <input
             ref={inputRef}
@@ -160,14 +162,14 @@ export function ChatBar({ t, sse }: {
             onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
             placeholder={chatPlaceholder}
             disabled={loading}
-            className="w-full bg-transparent text-[13px] text-foreground placeholder:text-muted-foreground/30 focus:outline-none disabled:opacity-50"
+            className="w-full bg-transparent text-[13px] text-foreground placeholder:text-muted-foreground/25 focus:outline-none disabled:opacity-50"
           />
         </div>
-        <div className="flex items-center gap-2 shrink-0">
+        <div className="flex items-center gap-3 shrink-0">
           {expanded && messages.length > 0 && (
             <button
               onClick={() => { setExpanded(false); setMessages([]); }}
-              className="text-[10px] text-muted-foreground/40 hover:text-muted-foreground transition-colors"
+              className="text-[10px] text-muted-foreground/30 hover:text-muted-foreground transition-colors"
             >
               Clear
             </button>
@@ -175,7 +177,7 @@ export function ChatBar({ t, sse }: {
           <button
             onClick={handleSubmit}
             disabled={!input.trim() || loading}
-            className="w-6 h-6 rounded bg-primary/80 text-primary-foreground flex items-center justify-center text-xs hover:bg-primary transition-colors disabled:opacity-20"
+            className="w-7 h-7 rounded-md bg-primary/70 text-primary-foreground flex items-center justify-center text-xs hover:bg-primary transition-all disabled:opacity-15"
           >
             ↑
           </button>

+ 22 - 22
packages/studio/src/components/Sidebar.tsx

@@ -27,36 +27,36 @@ export function Sidebar({ nav, activePage, t }: {
   const { data: daemon } = useApi<{ running: boolean }>("/daemon");
 
   return (
-    <aside className="w-[200px] shrink-0 border-r border-border/40 bg-card/50 flex flex-col h-full overflow-y-auto">
-      {/* Logo */}
-      <div className="px-4 py-5 border-b border-border/30">
-        <button onClick={nav.toDashboard} className="flex items-baseline gap-0.5 hover:opacity-80 transition-opacity">
-          <span className="font-serif text-xl italic text-primary">Ink</span>
-          <span className="text-base font-semibold tracking-tight">OS</span>
+    <aside className="w-[220px] shrink-0 border-r border-border/30 bg-card/30 flex flex-col h-full overflow-y-auto">
+      {/* Logo — generous vertical breathing room */}
+      <div className="px-6 pt-7 pb-6">
+        <button onClick={nav.toDashboard} className="flex items-baseline gap-0.5 hover:opacity-70 transition-opacity">
+          <span className="font-serif text-2xl italic text-primary">Ink</span>
+          <span className="text-lg font-semibold tracking-tight">OS</span>
         </button>
       </div>
 
       {/* Books section */}
-      <div className="flex-1 py-3">
-        <div className="px-4 mb-2 flex items-center justify-between">
-          <span className="text-[10px] uppercase tracking-widest text-muted-foreground/50">{t("nav.books")}</span>
+      <div className="flex-1 px-4">
+        <div className="px-2 mb-3 flex items-center justify-between">
+          <span className="text-[10px] uppercase tracking-[0.15em] text-muted-foreground/40 font-medium">{t("nav.books")}</span>
           <button
             onClick={nav.toBookCreate}
-            className="text-[10px] text-primary/60 hover:text-primary transition-colors"
+            className="w-5 h-5 flex items-center justify-center rounded text-[11px] text-muted-foreground/40 hover:text-primary hover:bg-primary/10 transition-all"
           >
             +
           </button>
         </div>
 
-        <div className="space-y-0.5 px-2">
+        <div className="space-y-1">
           {data?.books.map((book) => (
             <button
               key={book.id}
               onClick={() => nav.toBook(book.id)}
-              className={`w-full text-left px-2 py-1.5 rounded text-[13px] truncate transition-colors ${
+              className={`w-full text-left px-3 py-2 rounded-md text-[13px] truncate transition-all duration-150 ${
                 activePage === `book:${book.id}`
-                  ? "bg-primary/10 text-primary"
-                  : "text-muted-foreground hover:text-foreground hover:bg-muted/30"
+                  ? "bg-primary/10 text-primary font-medium"
+                  : "text-muted-foreground hover:text-foreground hover:bg-muted/40"
               }`}
             >
               {book.title}
@@ -64,15 +64,15 @@ export function Sidebar({ nav, activePage, t }: {
           ))}
 
           {(!data?.books || data.books.length === 0) && (
-            <div className="px-2 py-3 text-[11px] text-muted-foreground/40 italic">
+            <div className="px-3 py-4 text-[11px] text-muted-foreground/30 italic leading-relaxed">
               {t("dash.noBooks")}
             </div>
           )}
         </div>
       </div>
 
-      {/* System nav */}
-      <div className="border-t border-border/30 py-3 px-2 space-y-0.5">
+      {/* System nav — generous spacing from book list */}
+      <div className="border-t border-border/20 mt-4 pt-4 pb-5 px-4 space-y-1">
         <SidebarItem
           label={t("nav.config")}
           icon="⚙"
@@ -109,15 +109,15 @@ function SidebarItem({ label, icon, active, onClick, badge, badgeColor }: {
   return (
     <button
       onClick={onClick}
-      className={`w-full text-left px-2 py-1.5 rounded text-[13px] flex items-center gap-2 transition-colors ${
+      className={`w-full text-left px-3 py-2 rounded-md text-[13px] flex items-center gap-2.5 transition-all duration-150 ${
         active
-          ? "bg-secondary text-foreground"
-          : "text-muted-foreground hover:text-foreground hover:bg-muted/30"
+          ? "bg-secondary text-foreground font-medium"
+          : "text-muted-foreground hover:text-foreground hover:bg-muted/40"
       }`}
     >
-      <span className="text-xs w-4 text-center opacity-50">{icon}</span>
+      <span className="text-[11px] w-4 text-center opacity-40">{icon}</span>
       <span className="flex-1">{label}</span>
-      {badge && <span className={`text-[10px] ${badgeColor ?? ""}`}>{badge}</span>}
+      {badge && <span className={`text-[9px] ${badgeColor ?? ""}`}>{badge}</span>}
     </button>
   );
 }

+ 7 - 7
packages/studio/src/pages/Dashboard.tsx

@@ -47,16 +47,16 @@ export function Dashboard({ nav, sse, theme, t }: { nav: Nav; sse: { messages: R
   if (loading) return <div className="text-muted-foreground py-20 text-center text-sm">Loading...</div>;
   if (error) return <div className="text-destructive py-20 text-center">Error: {error}</div>;
 
-  /* ── Empty state ── */
+  /* ── Empty state — vertically centered in the available viewport ── */
   if (!data?.books.length) {
     return (
-      <div className="flex flex-col items-center justify-center py-32">
-        <div className="text-6xl mb-6 opacity-20">✦</div>
-        <h2 className="font-serif text-3xl italic text-foreground/50 mb-3">{t("dash.noBooks")}</h2>
-        <p className="text-sm text-muted-foreground mb-8">{t("dash.createFirst")}</p>
+      <div className="flex flex-col items-center justify-center min-h-[60vh]">
+        <div className="text-5xl mb-8 opacity-15 select-none">✦</div>
+        <h2 className="font-serif text-3xl italic text-foreground/40 mb-3">{t("dash.noBooks")}</h2>
+        <p className="text-sm text-muted-foreground/60 mb-10">{t("dash.createFirst")}</p>
         <button
           onClick={nav.toBookCreate}
-          className="px-6 py-3 rounded-md text-sm font-medium bg-primary text-primary-foreground hover:opacity-90 transition-opacity"
+          className="px-7 py-3 rounded-md text-sm font-medium bg-primary text-primary-foreground hover:opacity-90 transition-opacity"
         >
           {t("nav.newBook")}
         </button>
@@ -85,7 +85,7 @@ export function Dashboard({ nav, sse, theme, t }: { nav: Nav; sse: { messages: R
               key={book.id}
               className={`group border ${c.card} rounded-lg overflow-hidden`}
             >
-              <div className="px-5 py-4 flex items-start justify-between">
+              <div className="px-6 py-5 flex items-start justify-between">
                 <div className="min-w-0">
                   <button
                     onClick={() => nav.toBook(book.id)}