Skip to content

Commit 206829e

Browse files
committed
Release v1.6.0
1 parent 7c59ecd commit 206829e

9 files changed

Lines changed: 429 additions & 27 deletions

File tree

latest.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
2-
"version": "1.5.0",
3-
"notes": "Release v1.5.0.",
4-
"pub_date": "2026-03-02T18:13:54Z",
2+
"version": "1.6.0",
3+
"notes": "Release v1.6.0.",
4+
"pub_date": "2026-03-03T11:58:31Z",
55
"platforms": {
66
"darwin-aarch64": {
7-
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVUR2FwTVQvQmFrTk5tN1BZZEZON3gzNDZnR21iQ1FMWXFOSld2UStURHhqekdFWGI5K1BkM0FIeE9LdG5xcFhlazB1ZmZQakFnQ2RXSGRaTUt1OUROQWk5TUVhcllVcHc4PQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzcyNDc1MTc4CWZpbGU6SnVzdG8gRGV2NC5hcHAudGFyLmd6Cnhmd09HaW5WMXBBNU1PbTZnZHZ3WEJWWC9RM3Byei81ZDE0M1l6eWtGdFhwaTRqcjdZdndKdXdyWEFESnF1RGxaQlVIYjRQK1VQSDFXeTZoMFg1c0JBPT0K",
8-
"url": "https://github.com/getjusto/dev4/releases/download/v1.5.0/Justo.Dev4.app.tar.gz"
7+
"signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVUR2FwTVQvQmFrTkhqd1UvWVNzQ09RWVU3b0hkUGQyRjBiQU1YYi9iQ3FmRkxQRkFjbjZ5cXJ3c3hHWXhuSi9rSHNCMWNJRmJ2RkZaOUcyTmZnV3FiTEMzZ0VaM0tCUUFzPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzcyNTM5MDU0CWZpbGU6SnVzdG8gRGV2NC5hcHAudGFyLmd6CjlYbjZPaWJLNXFxQStzWW03QUtqU1c5K0ZzeWhaTUpYeXpxRjlaZ0RwWVhMUC93dW5mM2Q5SG9uOHlyNGFvY1Z3NlFhZjhuU3JGcGlIU1FrcXYrekFBPT0K",
8+
"url": "https://github.com/getjusto/dev4/releases/download/v1.6.0/Justo.Dev4.app.tar.gz"
99
}
1010
}
1111
}

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
"@tauri-apps/plugin-process": "~2",
3030
"@tauri-apps/plugin-shell": "~2",
3131
"@tauri-apps/plugin-updater": "~2.10.0",
32+
"@xterm/addon-fit": "^0.11.0",
33+
"@xterm/xterm": "^6.0.0",
3234
"ansi-to-react": "^6.1.6",
3335
"axios": "^1.8.4",
3436
"class-variance-authority": "^0.7.1",
@@ -41,10 +43,12 @@
4143
"react": "^18.3.1",
4244
"react-app-events": "^2.0.2",
4345
"react-dom": "^18.3.1",
46+
"react-resizable-panels": "^4.7.0",
4447
"react-router-dom": "^7.4.0",
4548
"react-syntax-highlighter": "^15.6.1",
4649
"sonner": "^2.0.1",
4750
"tailwind-merge": "^3.0.2",
51+
"tauri-pty": "^0.2.1",
4852
"tw-animate-css": "^1.2.4",
4953
"use-deep-compare-effect": "^1.8.1",
5054
"yaml": "^2.7.0"

pnpm-lock.yaml

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "Justo Dev4",
4-
"version": "1.5.0",
4+
"version": "1.6.0",
55
"identifier": "com.getjusto.dev4",
66
"build": {
77
"beforeDevCommand": "pnpm dev",

src/components/ui/resizable.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { GripVerticalIcon } from "lucide-react"
2+
import * as ResizablePrimitive from "react-resizable-panels"
3+
4+
import { cn } from "@/lib/utils"
5+
6+
function ResizablePanelGroup({
7+
className,
8+
...props
9+
}: ResizablePrimitive.GroupProps) {
10+
return (
11+
<ResizablePrimitive.Group
12+
data-slot="resizable-panel-group"
13+
className={cn(
14+
"flex h-full w-full aria-[orientation=vertical]:flex-col",
15+
className
16+
)}
17+
{...props}
18+
/>
19+
)
20+
}
21+
22+
function ResizablePanel({ ...props }: ResizablePrimitive.PanelProps) {
23+
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />
24+
}
25+
26+
function ResizableHandle({
27+
withHandle,
28+
className,
29+
...props
30+
}: ResizablePrimitive.SeparatorProps & {
31+
withHandle?: boolean
32+
}) {
33+
return (
34+
<ResizablePrimitive.Separator
35+
data-slot="resizable-handle"
36+
className={cn(
37+
"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-hidden aria-[orientation=horizontal]:h-px aria-[orientation=horizontal]:w-full aria-[orientation=horizontal]:after:left-0 aria-[orientation=horizontal]:after:h-1 aria-[orientation=horizontal]:after:w-full aria-[orientation=horizontal]:after:translate-x-0 aria-[orientation=horizontal]:after:-translate-y-1/2 [&[aria-orientation=horizontal]>div]:rotate-90",
38+
className
39+
)}
40+
{...props}
41+
>
42+
{withHandle && (
43+
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-xs border bg-border">
44+
<GripVerticalIcon className="size-2.5" />
45+
</div>
46+
)}
47+
</ResizablePrimitive.Separator>
48+
)
49+
}
50+
51+
export { ResizableHandle, ResizablePanel, ResizablePanelGroup }
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import {FitAddon} from '@xterm/addon-fit'
2+
import {Terminal as XTerm} from '@xterm/xterm'
3+
import {X} from 'lucide-react'
4+
import {useTheme} from 'next-themes'
5+
import {useEffect, useRef} from 'react'
6+
import '@xterm/xterm/css/xterm.css'
7+
import {getOrCreateSession} from './ptysessions'
8+
9+
const lightTheme = {
10+
background: '#ffffff',
11+
foreground: '#1e1e1e',
12+
cursor: '#1e1e1e',
13+
selectionBackground: '#add6ff',
14+
black: '#1e1e1e',
15+
red: '#cd3131',
16+
green: '#00bc7c',
17+
yellow: '#949800',
18+
blue: '#0451a5',
19+
magenta: '#bc05bc',
20+
cyan: '#0598bc',
21+
white: '#555555',
22+
brightBlack: '#666666',
23+
brightRed: '#cd3131',
24+
brightGreen: '#14ce14',
25+
brightYellow: '#b5ba00',
26+
brightBlue: '#0451a5',
27+
brightMagenta: '#bc05bc',
28+
brightCyan: '#0598bc',
29+
brightWhite: '#a5a5a5',
30+
}
31+
32+
const darkTheme = {
33+
background: '#1e1e1e',
34+
foreground: '#d4d4d4',
35+
cursor: '#d4d4d4',
36+
selectionBackground: '#264f78',
37+
black: '#1e1e1e',
38+
red: '#f44747',
39+
green: '#6a9955',
40+
yellow: '#d7ba7d',
41+
blue: '#569cd6',
42+
magenta: '#c586c0',
43+
cyan: '#4ec9b0',
44+
white: '#d4d4d4',
45+
brightBlack: '#808080',
46+
brightRed: '#f44747',
47+
brightGreen: '#6a9955',
48+
brightYellow: '#d7ba7d',
49+
brightBlue: '#569cd6',
50+
brightMagenta: '#c586c0',
51+
brightCyan: '#4ec9b0',
52+
brightWhite: '#e8e8e8',
53+
}
54+
55+
interface Props {
56+
sessionKey: string
57+
cwd: string
58+
onClose: () => void
59+
}
60+
61+
export default function Terminal({sessionKey, cwd, onClose}: Props) {
62+
const containerRef = useRef<HTMLDivElement>(null)
63+
const xtermRef = useRef<XTerm | null>(null)
64+
const fitAddonRef = useRef<FitAddon | null>(null)
65+
const {resolvedTheme} = useTheme()
66+
const isDark = resolvedTheme === 'dark'
67+
68+
useEffect(() => {
69+
if (xtermRef.current) {
70+
xtermRef.current.options.theme = isDark ? darkTheme : lightTheme
71+
}
72+
}, [isDark])
73+
74+
useEffect(() => {
75+
if (!containerRef.current) return
76+
77+
const theme = isDark ? darkTheme : lightTheme
78+
79+
const xterm = new XTerm({
80+
cursorBlink: true,
81+
fontSize: 13,
82+
fontFamily: '"Fira Code", "SF Mono", Menlo, Monaco, "Courier New", monospace',
83+
theme,
84+
allowProposedApi: true,
85+
scrollback: 5000,
86+
})
87+
88+
const fitAddon = new FitAddon()
89+
xterm.loadAddon(fitAddon)
90+
xterm.open(containerRef.current)
91+
92+
requestAnimationFrame(() => {
93+
fitAddon.fit()
94+
})
95+
96+
xtermRef.current = xterm
97+
fitAddonRef.current = fitAddon
98+
99+
const session = getOrCreateSession(sessionKey, cwd, xterm.cols, xterm.rows)
100+
101+
// Replay buffered output from previous views
102+
for (const chunk of session.buffer) {
103+
xterm.write(chunk)
104+
}
105+
106+
// Connect live output forwarding
107+
session.writeToXterm = (text: string) => xterm.write(text)
108+
109+
// Keyboard input -> PTY
110+
const inputListener = xterm.onData((data: string) => {
111+
session.pty.write(data)
112+
})
113+
114+
// PTY exit -> close panel
115+
const exitListener = session.pty.onExit(() => {
116+
onClose()
117+
})
118+
119+
// Resize PTY when container resizes
120+
const observer = new ResizeObserver(() => {
121+
requestAnimationFrame(() => {
122+
if (fitAddonRef.current && xtermRef.current) {
123+
fitAddonRef.current.fit()
124+
session.pty.resize(xtermRef.current.cols, xtermRef.current.rows)
125+
}
126+
})
127+
})
128+
observer.observe(containerRef.current)
129+
130+
// Let CMD+J close the terminal (only on keydown)
131+
xterm.attachCustomKeyEventHandler((event: KeyboardEvent) => {
132+
if (event.type === 'keydown' && (event.metaKey || event.ctrlKey) && event.key === 'j') {
133+
event.preventDefault()
134+
onClose()
135+
return false
136+
}
137+
return true
138+
})
139+
140+
xterm.focus()
141+
142+
return () => {
143+
observer.disconnect()
144+
session.writeToXterm = null
145+
inputListener.dispose()
146+
exitListener.dispose()
147+
xterm.dispose()
148+
xtermRef.current = null
149+
fitAddonRef.current = null
150+
}
151+
}, [sessionKey, cwd])
152+
153+
return (
154+
<div className="flex flex-col h-full">
155+
<div className="flex items-center justify-between px-3 py-1 bg-muted/50 border-b border-border">
156+
<span className="text-xs text-muted-foreground font-medium">Terminal</span>
157+
<button
158+
type="button"
159+
onClick={onClose}
160+
className="p-0.5 rounded hover:bg-muted-foreground/20 text-muted-foreground"
161+
title="Close terminal (⌘J)"
162+
>
163+
<X size={14} />
164+
</button>
165+
</div>
166+
<div className="flex-1 min-h-0 overflow-hidden p-2">
167+
<div ref={containerRef} className="h-full w-full" />
168+
</div>
169+
</div>
170+
)
171+
}

0 commit comments

Comments
 (0)