Skip to content

Commit 3fb3985

Browse files
davindicodeclaude
andcommitted
Add MIT license, Ctrl+ full alphabet, nav polish, download buttons
- MIT license - Ctrl+ group: all A-Z keys instead of curated list - Persistent nav keys below input: added Backspace - Removed Nav from drill-in groups (now always visible) - Download button on all file viewers (code editor, image) - Mobile tab bar reordered: Files | Terminal | Browser (matches desktop) - Terminal tabs moved to footer position - Placeholder: "Type anything..." Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 86f513a commit 3fb3985

5 files changed

Lines changed: 88 additions & 40 deletions

File tree

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 davindicode
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

app/components/MobileTabBar.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@ import { useUiStore, type TabId } from "~/stores/uiStore";
22

33
const tabs: { id: TabId; label: string; icon: React.ReactNode }[] = [
44
{
5-
id: "terminal",
6-
label: "Terminal",
5+
id: "files",
6+
label: "Files",
77
icon: (
88
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
9-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
9+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
1010
</svg>
1111
),
1212
},
1313
{
14-
id: "files",
15-
label: "Files",
14+
id: "terminal",
15+
label: "Terminal",
1616
icon: (
1717
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
18-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
18+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
1919
</svg>
2020
),
2121
},

app/components/files/CodeEditor.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,13 @@ export default function CodeEditor({ path, content, onSave, onClose }: CodeEdito
235235
</button>
236236
</div>
237237
)}
238+
<a
239+
href={`/api/files/download?path=${encodeURIComponent(path)}`}
240+
download
241+
className="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-white rounded text-xs transition-colors"
242+
>
243+
Download
244+
</a>
238245
{dirty && <span className="text-xs text-yellow-500">Unsaved</span>}
239246
<button
240247
onClick={handleSave}

app/components/files/ImageViewer.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,21 @@ export default function ImageViewer({ path, onClose }: ImageViewerProps) {
88
<div className="flex flex-col h-full">
99
<div className="flex items-center justify-between px-3 py-2 bg-[#16162a] border-b border-gray-700 shrink-0">
1010
<span className="text-sm text-gray-300 truncate">{path}</span>
11-
<button
12-
onClick={onClose}
13-
className="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-white rounded text-xs transition-colors"
14-
>
15-
Close
16-
</button>
11+
<div className="flex items-center gap-2 shrink-0">
12+
<a
13+
href={`/api/files/download?path=${encodeURIComponent(path)}`}
14+
download
15+
className="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-white rounded text-xs transition-colors"
16+
>
17+
Download
18+
</a>
19+
<button
20+
onClick={onClose}
21+
className="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-white rounded text-xs transition-colors"
22+
>
23+
Close
24+
</button>
25+
</div>
1726
</div>
1827
<div className="flex-1 flex items-center justify-center overflow-auto p-4 bg-[#0d0d1a]">
1928
<img

app/components/terminal/InputBox.tsx

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,35 +13,30 @@ interface QuickKeyGroup {
1313
keys: QuickKey[];
1414
}
1515

16+
// Always-visible nav keys below the text input
17+
const NAV_KEYS: QuickKey[] = [
18+
{ label: "Tab", key: "\t", title: "Autocomplete" },
19+
{ label: "Enter", key: "\r", title: "Enter / Confirm" },
20+
{ label: "Bksp", key: "\x7f", title: "Backspace" },
21+
{ label: "Esc", key: "\x1b", title: "Escape" },
22+
{ label: "Up", key: "\x1b[A", title: "Previous command" },
23+
{ label: "Down", key: "\x1b[B", title: "Next command" },
24+
{ label: "Left", key: "\x1b[D", title: "Cursor left" },
25+
{ label: "Right", key: "\x1b[C", title: "Cursor right" },
26+
{ label: "y", key: "y", title: "Yes" },
27+
{ label: "n", key: "n", title: "No" },
28+
{ label: "q", key: "q", title: "Quit (htop/less)" },
29+
];
30+
1631
const GROUPS: QuickKeyGroup[] = [
1732
{
18-
label: "Ctrl",
19-
title: "Control combos",
20-
keys: [
21-
{ label: "Ctrl+C", key: "\x03", title: "Interrupt" },
22-
{ label: "Ctrl+D", key: "\x04", title: "EOF / Exit" },
23-
{ label: "Ctrl+Z", key: "\x1a", title: "Suspend" },
24-
{ label: "Ctrl+L", key: "\x0c", title: "Clear screen" },
25-
{ label: "Ctrl+A", key: "\x01", title: "Home" },
26-
{ label: "Ctrl+E", key: "\x05", title: "End" },
27-
{ label: "Ctrl+R", key: "\x12", title: "Reverse search" },
28-
],
29-
},
30-
{
31-
label: "Nav",
32-
title: "Navigation keys",
33-
keys: [
34-
{ label: "Enter", key: "\r", title: "Enter / Confirm" },
35-
{ label: "Tab", key: "\t", title: "Autocomplete" },
36-
{ label: "Esc", key: "\x1b", title: "Escape" },
37-
{ label: "Up", key: "\x1b[A", title: "Previous command" },
38-
{ label: "Down", key: "\x1b[B", title: "Next command" },
39-
{ label: "Left", key: "\x1b[D", title: "Cursor left" },
40-
{ label: "Right", key: "\x1b[C", title: "Cursor right" },
41-
{ label: "q", key: "q", title: "Quit (htop/less)" },
42-
{ label: "y", key: "y", title: "Yes" },
43-
{ label: "n", key: "n", title: "No" },
44-
],
33+
label: "Ctrl+",
34+
title: "Ctrl + key combos",
35+
keys: "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").map((ch) => ({
36+
label: ch,
37+
key: String.fromCharCode(ch.charCodeAt(0) - 64), // Ctrl+A = 0x01, Ctrl+Z = 0x1A
38+
title: `Ctrl+${ch}`,
39+
})),
4540
},
4641
{
4742
label: "nano",
@@ -204,7 +199,7 @@ export default function InputBox() {
204199
value={text}
205200
onChange={(e) => setText(e.target.value)}
206201
onKeyDown={handleKeyDown}
207-
placeholder="Type a command..."
202+
placeholder="Type anything..."
208203
rows={1}
209204
className="flex-1 bg-[#1a1a2e] text-white border border-gray-600 rounded-lg px-3 py-2 text-sm resize-none focus:outline-none focus:ring-1 focus:ring-blue-500"
210205
/>
@@ -216,6 +211,22 @@ export default function InputBox() {
216211
Send
217212
</button>
218213
</div>
214+
{/* Persistent nav keys */}
215+
<div className="border-t border-gray-700/50 overflow-x-auto scrollbar-none" style={{ minWidth: 0 }}>
216+
<div className="flex items-center gap-1 px-2 py-1 w-max">
217+
{NAV_KEYS.map((qk) => (
218+
<button
219+
key={qk.label}
220+
onClick={() => handleQuickKey(qk.key)}
221+
disabled={!activeSessionId}
222+
title={qk.title}
223+
className="px-2 py-0.5 text-[11px] bg-[#1a1a2e] text-gray-400 hover:text-white hover:bg-[#2a2a4a] disabled:text-gray-600 rounded border border-gray-700 whitespace-nowrap transition-colors shrink-0"
224+
>
225+
{qk.label}
226+
</button>
227+
))}
228+
</div>
229+
</div>
219230
</div>
220231
);
221232
}

0 commit comments

Comments
 (0)