Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
scripts/verify-ghostty-resources.sh build/Build/Products/Debug/Glint.app

- name: Test Web Remote browser input
run: node --test scripts/test-web-remote-ime.mjs
run: node --test scripts/test-web-remote-*.mjs

- name: Test Glint
run: |
Expand Down
19 changes: 19 additions & 0 deletions Glint/App/ReleaseNotes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,25 @@ enum ReleaseNotes {
/// to that placeholder later got mis-attributed). See CLAUDE.md
/// "发版「更新内容」" for the release-time workflow.
static let all: [ReleaseNote] = [
ReleaseNote(
version: "0.1.28-beta.6",
en: [
"Browse the current terminal folder from Web Remote and preview text, images, and static HTML. Image previews support pinch zoom and dragging.",
"File previews stay within the selected folder even while files are being replaced. Changing the terminal directory refreshes the browser, and late responses can no longer show content from the wrong folder.",
"Web Remote automatically resumes listening when the selected network interface recovers after a Wi-Fi change or sleep, without widening access to other interfaces.",
"Shared Codex daemon events now update the matching terminal instead of leaving another pane stuck thinking or overwriting its resume session.",
"Managed shell configuration blocks no longer swallow the next line into a comment, and inline suggestions respect an existing zsh-autosuggestions setup.",
"Switching workspaces more reliably restores split terminals. Accessibility tools can identify the current workspace and selected sidebar card in both sidebar layouts."
],
zh: [
"网页远程端支持浏览当前终端目录,预览文本、图片和静态 HTML;图片支持双指缩放和拖动。",
"文件在并发替换时也不会绕过所选目录的访问限制。终端切换目录后,网页会刷新文件列表,迟到的响应不再显示其他目录的内容。",
"Wi-Fi 切换或睡眠唤醒后,所选网卡恢复可用时,网页远程服务会自动恢复监听,不会扩大到其他网卡。",
"共享 Codex daemon 的事件会更新对应终端,不再让其他窗格卡在思考中或覆盖其会话恢复信息。",
"托管 shell 配置块不再把下一行粘成注释,命令提示也会尊重已有的 zsh-autosuggestions 配置。",
"切换工作区时,分屏终端的恢复更加可靠;侧栏展开和折叠时,辅助功能工具都能识别当前工作区和选中的侧栏卡片。"
]
),
ReleaseNote(
version: "0.1.28-beta.4",
en: [
Expand Down
71 changes: 55 additions & 16 deletions Glint/Resources/WebRemote/web-remote.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ const translations = {
image_unavailable: "无法解码此图片",
files_limited: "仅显示前 200 项",
files_unavailable: "当前终端没有可访问的本地目录",
file_root_changed: "终端目录已变化,正在刷新…",
invalid_file_path: "文件路径无效或超出项目目录",
file_unavailable: "文件或目录无法读取",
file_too_large: "文件超过 128 KB,无法预览",
Expand Down Expand Up @@ -161,6 +162,7 @@ const translations = {
image_unavailable: "Unable to decode this image",
files_limited: "Showing the first 200 items",
files_unavailable: "This terminal has no accessible local directory",
file_root_changed: "The terminal directory changed. Refreshing…",
invalid_file_path: "File path is invalid or outside the workspace",
file_unavailable: "File or folder cannot be read",
file_too_large: "Files over 128 KB cannot be previewed",
Expand Down Expand Up @@ -343,6 +345,10 @@ let appliedThemeSignature = "";
let fileWorkspace = "";
let filePane = "";
let fileRoot = "";
let filePaneCwd = "";
let fileRequestSequence = 0;
let fileListRequest = "";
let fileContentRequest = "";
let fileDirectory = "";
let filePath = "";
let htmlSource = "";
Expand Down Expand Up @@ -541,20 +547,30 @@ function handleMessage(raw) {
renderState(message);
chooseInitialPane(message);
updateFilesButton();
if (filePane && !message.workspaces.some(item =>
item.id === fileWorkspace && item.panes?.some(pane => pane.id === filePane))) {
closeFiles();
if (filePane) {
const pane = message.workspaces.find(item => item.id === fileWorkspace)
?.panes?.find(pane => pane.id === filePane);
if (!pane) {
closeFiles();
} else if ((pane.cwd || "") !== filePaneCwd) {
filePaneCwd = pane.cwd || "";
fileRoot = "";
browseFiles("");
}
}
break;
case "fileList":
if (message.workspace === fileWorkspace && message.pane === filePane && message.path === fileDirectory) {
if (matchesFileResponse(message, fileListRequest, fileDirectory)
&& (!fileRoot || message.root === fileRoot)) {
fileListRequest = "";
fileRoot = message.root || "";
updateFileLocation();
renderFiles(message.entries || [], message.limit);
}
break;
case "fileContent":
if (message.workspace === fileWorkspace && message.pane === filePane && message.path === filePath) {
if (matchesFileResponse(message, fileContentRequest, filePath) && message.root === fileRoot) {
fileContentRequest = "";
if (message.format === "html") {
htmlSource = message.content;
htmlSourceShown = false;
Expand All @@ -571,7 +587,8 @@ function handleMessage(raw) {
}
break;
case "fileImage":
if (message.workspace === fileWorkspace && message.pane === filePane && message.path === filePath) {
if (matchesFileResponse(message, fileContentRequest, filePath) && message.root === fileRoot) {
fileContentRequest = "";
elements.filesImage.src = `data:image/png;base64,${message.data}`;
elements.filesImage.alt = elements.filesPreviewName.textContent;
imageZoom.reset();
Expand All @@ -580,16 +597,25 @@ function handleMessage(raw) {
elements.filesMessage.textContent = "";
}
break;
case "fileError":
if (message.workspace !== fileWorkspace || message.pane !== filePane) break;
if (message.path === filePath && filePath) {
elements.filesContent.textContent = "";
elements.filesMessage.textContent = fileErrorLabel(message.code);
} else if (message.path === fileDirectory) {
case "fileError": {
const reading = matchesFileResponse(message, fileContentRequest, filePath);
const listing = matchesFileResponse(message, fileListRequest, fileDirectory);
if (!reading && !listing) break;
if (message.code === "file-root-changed") {
fileRoot = "";
browseFiles("");
break;
}
if (reading) {
fileContentRequest = "";
clearFilePreview();
} else {
fileListRequest = "";
elements.filesList.replaceChildren();
elements.filesMessage.textContent = fileErrorLabel(message.code);
}
elements.filesMessage.textContent = fileErrorLabel(message.code);
break;
}
case "snapshot":
if (message.pane !== selectedPane) return;
clearTimeout(paneRetryTimer);
Expand Down Expand Up @@ -774,6 +800,7 @@ function errorLabel(code) {
function fileErrorLabel(code) {
return t({
"files-unavailable": "files_unavailable",
"file-root-changed": "file_root_changed",
"invalid-file-path": "invalid_file_path",
"file-unavailable": "file_unavailable",
"file-too-large": "file_too_large",
Expand Down Expand Up @@ -922,6 +949,11 @@ function clearFilePreview() {
elements.filesSourceToggle.hidden = true;
}

function matchesFileResponse(message, request, path) {
return !!request && message.request === request && message.workspace === fileWorkspace
&& message.pane === filePane && message.path === path;
}

function updateFilesButton() {
elements.openFiles.hidden = !authenticated || !selectedPane;
}
Expand All @@ -939,7 +971,8 @@ function openFiles() {
if (!workspace) return;
fileWorkspace = workspace.id;
filePane = selectedPane;
fileRoot = workspace.panes.find(pane => pane.id === selectedPane)?.cwd || "";
filePaneCwd = workspace.panes.find(pane => pane.id === selectedPane)?.cwd || "";
fileRoot = "";
fileDirectory = "";
filePath = "";
elements.filesPanel.hidden = false;
Expand All @@ -955,6 +988,9 @@ function closeFiles() {
fileWorkspace = "";
filePane = "";
fileRoot = "";
filePaneCwd = "";
fileListRequest = "";
fileContentRequest = "";
filePath = "";
clearFilePreview();
elements.filesPanel.hidden = true;
Expand All @@ -965,13 +1001,15 @@ function closeFiles() {
function browseFiles(path) {
fileDirectory = path;
filePath = "";
fileContentRequest = "";
fileListRequest = String(++fileRequestSequence);
updateFileLocation();
elements.filesUp.disabled = !path;
elements.filesList.replaceChildren();
elements.filesPreviewName.textContent = "";
clearFilePreview();
elements.filesMessage.textContent = t("files_loading");
send({ type: "listFiles", workspace: fileWorkspace, pane: filePane, path });
send({ type: "listFiles", workspace: fileWorkspace, pane: filePane, path, root: fileRoot, request: fileListRequest });
}

function renderFiles(entries, limit) {
Expand All @@ -991,7 +1029,8 @@ function renderFiles(entries, limit) {
elements.filesPreviewName.textContent = entry.name;
clearFilePreview();
elements.filesMessage.textContent = t("files_loading");
send({ type: "readFile", workspace: fileWorkspace, pane: filePane, path });
fileContentRequest = String(++fileRequestSequence);
send({ type: "readFile", workspace: fileWorkspace, pane: filePane, path, root: fileRoot, request: fileContentRequest });
}
});
elements.filesList.append(button);
Expand Down
Loading
Loading