codeblog-app 1.5.1 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "codeblog-app",
4
- "version": "1.5.1",
4
+ "version": "1.5.2",
5
5
  "description": "CLI client for CodeBlog — the forum where AI writes the posts",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -56,11 +56,11 @@
56
56
  "typescript": "5.8.2"
57
57
  },
58
58
  "optionalDependencies": {
59
- "codeblog-app-darwin-arm64": "1.5.1",
60
- "codeblog-app-darwin-x64": "1.5.1",
61
- "codeblog-app-linux-arm64": "1.5.1",
62
- "codeblog-app-linux-x64": "1.5.1",
63
- "codeblog-app-windows-x64": "1.5.1"
59
+ "codeblog-app-darwin-arm64": "1.5.2",
60
+ "codeblog-app-darwin-x64": "1.5.2",
61
+ "codeblog-app-linux-arm64": "1.5.2",
62
+ "codeblog-app-linux-x64": "1.5.2",
63
+ "codeblog-app-windows-x64": "1.5.2"
64
64
  },
65
65
  "dependencies": {
66
66
  "@ai-sdk/amazon-bedrock": "^4.0.60",
package/src/tui/app.tsx CHANGED
@@ -5,7 +5,7 @@ import { ExitProvider, useExit } from "./context/exit"
5
5
  import { ThemeProvider, useTheme } from "./context/theme"
6
6
  import { Home } from "./routes/home"
7
7
  import { Chat } from "./routes/chat"
8
- import { ThemeSetup } from "./routes/setup"
8
+ import { ThemeSetup, ThemePicker } from "./routes/setup"
9
9
 
10
10
  import pkg from "../../package.json"
11
11
  const VERSION = pkg.version
@@ -123,6 +123,9 @@ function App() {
123
123
  <Match when={route.data.type === "chat"}>
124
124
  <Chat />
125
125
  </Match>
126
+ <Match when={route.data.type === "theme"}>
127
+ <ThemePicker onDone={() => route.navigate({ type: "home" })} />
128
+ </Match>
126
129
  </Switch>
127
130
 
128
131
  {/* Status bar */}
@@ -8,7 +8,9 @@ export type SearchRoute = { type: "search"; query: string }
8
8
  export type TrendingRoute = { type: "trending" }
9
9
  export type NotificationsRoute = { type: "notifications" }
10
10
 
11
- export type Route = HomeRoute | ChatRoute | PostRoute | SearchRoute | TrendingRoute | NotificationsRoute
11
+ export type ThemeRoute = { type: "theme" }
12
+
13
+ export type Route = HomeRoute | ChatRoute | PostRoute | SearchRoute | TrendingRoute | NotificationsRoute | ThemeRoute
12
14
 
13
15
  export const { use: useRoute, provider: RouteProvider } = createSimpleContext({
14
16
  name: "Route",
@@ -2,7 +2,7 @@ import { createSignal, Show } from "solid-js"
2
2
  import { useKeyboard } from "@opentui/solid"
3
3
  import { useRoute } from "../context/route"
4
4
  import { useExit } from "../context/exit"
5
- import { useTheme, THEME_NAMES } from "../context/theme"
5
+ import { useTheme } from "../context/theme"
6
6
 
7
7
  const LOGO = [
8
8
  " ██████╗ ██████╗ ██████╗ ███████╗██████╗ ██╗ ██████╗ ██████╗ ",
@@ -82,30 +82,10 @@ export function Home(props: {
82
82
  return
83
83
  }
84
84
 
85
- if (cmd === "/theme") {
86
- const name = parts[1]
87
- if (!name) {
88
- showMsg(`Theme: ${theme.name} (${theme.mode}) | Available: ${THEME_NAMES.join(", ")}`, theme.colors.text)
89
- return
90
- }
91
- if (THEME_NAMES.includes(name)) {
92
- theme.set(name)
93
- showMsg(`Theme set to ${name}`, theme.colors.success)
94
- } else {
95
- showMsg(`Unknown theme: ${name}. Available: ${THEME_NAMES.join(", ")}`, theme.colors.error)
96
- }
97
- return
98
- }
99
-
100
- if (cmd === "/dark") {
101
- theme.setMode("dark")
102
- showMsg("Switched to dark mode", theme.colors.success)
103
- return
104
- }
105
-
106
- if (cmd === "/light") {
107
- theme.setMode("light")
108
- showMsg("Switched to light mode", theme.colors.success)
85
+ if (cmd === "/theme" || cmd === "/dark" || cmd === "/light") {
86
+ if (cmd === "/dark") theme.setMode("dark")
87
+ if (cmd === "/light") theme.setMode("light")
88
+ route.navigate({ type: "theme" })
109
89
  return
110
90
  }
111
91
 
@@ -2,6 +2,14 @@ import { createSignal } from "solid-js"
2
2
  import { useKeyboard } from "@opentui/solid"
3
3
  import { useTheme, THEME_NAMES, THEMES } from "../context/theme"
4
4
 
5
+ // High-contrast colors that are visible on ANY terminal background
6
+ const HC = {
7
+ title: "#ff6600",
8
+ text: "#888888",
9
+ selected: "#00cc00",
10
+ dim: "#999999",
11
+ }
12
+
5
13
  export function ThemeSetup() {
6
14
  const theme = useTheme()
7
15
  const modes = ["dark", "light"] as const
@@ -13,13 +21,11 @@ export function ThemeSetup() {
13
21
  if (step() === "mode") {
14
22
  if (evt.name === "up" || evt.name === "k") {
15
23
  setModeIdx((i) => (i - 1 + modes.length) % modes.length)
16
- theme.setMode(modes[(modeIdx() - 1 + modes.length) % modes.length])
17
24
  evt.preventDefault()
18
25
  return
19
26
  }
20
27
  if (evt.name === "down" || evt.name === "j") {
21
28
  setModeIdx((i) => (i + 1) % modes.length)
22
- theme.setMode(modes[(modeIdx() + 1) % modes.length])
23
29
  evt.preventDefault()
24
30
  return
25
31
  }
@@ -33,14 +39,16 @@ export function ThemeSetup() {
33
39
 
34
40
  if (step() === "theme") {
35
41
  if (evt.name === "up" || evt.name === "k") {
36
- setThemeIdx((i) => (i - 1 + THEME_NAMES.length) % THEME_NAMES.length)
37
- theme.set(THEME_NAMES[(themeIdx() - 1 + THEME_NAMES.length) % THEME_NAMES.length])
42
+ const next = (themeIdx() - 1 + THEME_NAMES.length) % THEME_NAMES.length
43
+ setThemeIdx(next)
44
+ theme.set(THEME_NAMES[next])
38
45
  evt.preventDefault()
39
46
  return
40
47
  }
41
48
  if (evt.name === "down" || evt.name === "j") {
42
- setThemeIdx((i) => (i + 1) % THEME_NAMES.length)
43
- theme.set(THEME_NAMES[(themeIdx() + 1) % THEME_NAMES.length])
49
+ const next = (themeIdx() + 1) % THEME_NAMES.length
50
+ setThemeIdx(next)
51
+ theme.set(THEME_NAMES[next])
44
52
  evt.preventDefault()
45
53
  return
46
54
  }
@@ -62,62 +70,62 @@ export function ThemeSetup() {
62
70
  <box flexGrow={1} minHeight={0} />
63
71
 
64
72
  <box flexShrink={0} flexDirection="column" alignItems="center">
65
- <text fg={theme.colors.primary}>
66
- <span style={{ bold: true }}>Welcome to CodeBlog</span>
73
+ <text fg={HC.title}>
74
+ <span style={{ bold: true }}>{"★ Welcome to CodeBlog ★"}</span>
67
75
  </text>
68
76
  <box height={1} />
69
- <text fg={theme.colors.textMuted}>Let's set up your terminal theme</text>
70
- <box height={1} />
71
77
  </box>
72
78
 
73
79
  {step() === "mode" ? (
74
80
  <box flexShrink={0} flexDirection="column" width="100%" maxWidth={50}>
75
- <text fg={theme.colors.text}>
76
- <span style={{ bold: true }}>Step 1: Is your terminal background dark or light?</span>
81
+ <text fg={HC.title}>
82
+ <span style={{ bold: true }}>{"What is your terminal background color?"}</span>
77
83
  </text>
78
84
  <box height={1} />
79
85
  {modes.map((m, i) => (
80
86
  <box flexDirection="row" paddingLeft={2}>
81
- <text fg={modeIdx() === i ? theme.colors.primary : theme.colors.textMuted}>
87
+ <text fg={modeIdx() === i ? HC.selected : HC.dim}>
82
88
  {modeIdx() === i ? "❯ " : " "}
83
89
  </text>
84
- <text fg={modeIdx() === i ? theme.colors.text : theme.colors.textMuted}>
90
+ <text fg={modeIdx() === i ? HC.selected : HC.dim}>
85
91
  <span style={{ bold: modeIdx() === i }}>
86
- {m === "dark" ? "🌙 Dark background" : "☀️ Light background"}
92
+ {m === "dark" ? "Dark background (black/dark terminal)" : "Light background (white/light terminal)"}
87
93
  </span>
88
94
  </text>
89
95
  </box>
90
96
  ))}
91
97
  <box height={1} />
92
- <text fg={theme.colors.textMuted}>↑↓ to select · Enter to confirm</text>
98
+ <text fg={HC.text}>{"↑↓ select · Enter confirm"}</text>
93
99
  </box>
94
100
  ) : (
95
- <box flexShrink={0} flexDirection="column" width="100%" maxWidth={50}>
101
+ <box flexShrink={0} flexDirection="column" width="100%" maxWidth={60}>
96
102
  <text fg={theme.colors.text}>
97
- <span style={{ bold: true }}>Step 2: Choose a color theme</span>
103
+ <span style={{ bold: true }}>{"Choose a color theme:"}</span>
98
104
  </text>
99
105
  <box height={1} />
100
106
  {THEME_NAMES.map((name, i) => {
101
- const def = THEMES[name]
102
- const c = def[theme.mode]
107
+ const c = THEMES[name][theme.mode]
103
108
  return (
104
109
  <box flexDirection="row" paddingLeft={2}>
105
110
  <text fg={themeIdx() === i ? c.primary : theme.colors.textMuted}>
106
111
  {themeIdx() === i ? "❯ " : " "}
107
112
  </text>
108
113
  <text fg={themeIdx() === i ? c.text : theme.colors.textMuted}>
109
- <span style={{ bold: themeIdx() === i }}>{name}</span>
114
+ <span style={{ bold: themeIdx() === i }}>
115
+ {name.padEnd(14)}
116
+ </span>
110
117
  </text>
111
118
  <text fg={c.logo1}>{" ●"}</text>
112
119
  <text fg={c.logo2}>{"●"}</text>
113
120
  <text fg={c.primary}>{"●"}</text>
121
+ <text fg={c.accent}>{"●"}</text>
114
122
  <text fg={c.success}>{"●"}</text>
115
123
  <text fg={c.error}>{"●"}</text>
116
124
  </box>
117
125
  )
118
126
  })}
119
127
  <box height={1} />
120
- <text fg={theme.colors.textMuted}>↑↓ to select · Enter to confirm · Esc to go back</text>
128
+ <text fg={theme.colors.textMuted}>{"↑↓ select · Enter confirm · Esc back"}</text>
121
129
  </box>
122
130
  )}
123
131
 
@@ -125,3 +133,123 @@ export function ThemeSetup() {
125
133
  </box>
126
134
  )
127
135
  }
136
+
137
+ export function ThemePicker(props: { onDone: () => void }) {
138
+ const theme = useTheme()
139
+ const [idx, setIdx] = createSignal(THEME_NAMES.indexOf(theme.name))
140
+ const [tab, setTab] = createSignal<"theme" | "mode">("theme")
141
+
142
+ useKeyboard((evt) => {
143
+ if (tab() === "theme") {
144
+ if (evt.name === "up" || evt.name === "k") {
145
+ const next = (idx() - 1 + THEME_NAMES.length) % THEME_NAMES.length
146
+ setIdx(next)
147
+ theme.set(THEME_NAMES[next])
148
+ evt.preventDefault()
149
+ return
150
+ }
151
+ if (evt.name === "down" || evt.name === "j") {
152
+ const next = (idx() + 1) % THEME_NAMES.length
153
+ setIdx(next)
154
+ theme.set(THEME_NAMES[next])
155
+ evt.preventDefault()
156
+ return
157
+ }
158
+ if (evt.name === "tab") {
159
+ setTab("mode")
160
+ evt.preventDefault()
161
+ return
162
+ }
163
+ if (evt.name === "return" || evt.name === "escape") {
164
+ props.onDone()
165
+ evt.preventDefault()
166
+ return
167
+ }
168
+ }
169
+
170
+ if (tab() === "mode") {
171
+ if (evt.name === "up" || evt.name === "down" || evt.name === "k" || evt.name === "j") {
172
+ theme.setMode(theme.mode === "dark" ? "light" : "dark")
173
+ evt.preventDefault()
174
+ return
175
+ }
176
+ if (evt.name === "tab") {
177
+ setTab("theme")
178
+ evt.preventDefault()
179
+ return
180
+ }
181
+ if (evt.name === "return" || evt.name === "escape") {
182
+ props.onDone()
183
+ evt.preventDefault()
184
+ return
185
+ }
186
+ }
187
+ })
188
+
189
+ return (
190
+ <box flexDirection="column" flexGrow={1} paddingLeft={2} paddingRight={2} paddingTop={1}>
191
+ <box flexDirection="row" gap={4} flexShrink={0}>
192
+ <text fg={theme.colors.primary}>
193
+ <span style={{ bold: true }}>Theme Settings</span>
194
+ </text>
195
+ <box flexGrow={1} />
196
+ <text fg={theme.colors.textMuted}>{"Tab: switch section · Enter/Esc: done"}</text>
197
+ </box>
198
+
199
+ <box flexDirection="row" flexGrow={1} paddingTop={1} gap={4}>
200
+ {/* Theme list */}
201
+ <box flexDirection="column" width={40}>
202
+ <text fg={tab() === "theme" ? theme.colors.text : theme.colors.textMuted}>
203
+ <span style={{ bold: true }}>{"Color Theme"}</span>
204
+ </text>
205
+ <box height={1} />
206
+ {THEME_NAMES.map((name, i) => {
207
+ const c = THEMES[name][theme.mode]
208
+ return (
209
+ <box flexDirection="row">
210
+ <text fg={idx() === i ? c.primary : theme.colors.textMuted}>
211
+ {idx() === i && tab() === "theme" ? "❯ " : " "}
212
+ </text>
213
+ <text fg={idx() === i ? c.text : theme.colors.textMuted}>
214
+ <span style={{ bold: idx() === i }}>
215
+ {name.padEnd(14)}
216
+ </span>
217
+ </text>
218
+ <text fg={c.logo1}>{" ●"}</text>
219
+ <text fg={c.logo2}>{"●"}</text>
220
+ <text fg={c.primary}>{"●"}</text>
221
+ <text fg={c.accent}>{"●"}</text>
222
+ <text fg={c.success}>{"●"}</text>
223
+ <text fg={c.error}>{"●"}</text>
224
+ </box>
225
+ )
226
+ })}
227
+ </box>
228
+
229
+ {/* Mode toggle */}
230
+ <box flexDirection="column" width={30}>
231
+ <text fg={tab() === "mode" ? theme.colors.text : theme.colors.textMuted}>
232
+ <span style={{ bold: true }}>{"Background Mode"}</span>
233
+ </text>
234
+ <box height={1} />
235
+ <box flexDirection="row">
236
+ <text fg={theme.mode === "dark" && tab() === "mode" ? theme.colors.primary : theme.colors.textMuted}>
237
+ {theme.mode === "dark" && tab() === "mode" ? "❯ " : " "}
238
+ </text>
239
+ <text fg={theme.mode === "dark" ? theme.colors.text : theme.colors.textMuted}>
240
+ <span style={{ bold: theme.mode === "dark" }}>{"Dark"}</span>
241
+ </text>
242
+ </box>
243
+ <box flexDirection="row">
244
+ <text fg={theme.mode === "light" && tab() === "mode" ? theme.colors.primary : theme.colors.textMuted}>
245
+ {theme.mode === "light" && tab() === "mode" ? "❯ " : " "}
246
+ </text>
247
+ <text fg={theme.mode === "light" ? theme.colors.text : theme.colors.textMuted}>
248
+ <span style={{ bold: theme.mode === "light" }}>{"Light"}</span>
249
+ </text>
250
+ </box>
251
+ </box>
252
+ </box>
253
+ </box>
254
+ )
255
+ }