Commit 67a96c3
J Code: native Android IDE — editor, terminals, Build & Run, and an extension marketplace (#1)
* initial stuffs
* Add verification plan, mock UI, and update project structure
- Created a new verification plan document for J Code, detailing phase checklists for feature validation.
- Added a mock UI image to the plans directory.
- Updated settings.gradle.kts to include new core and feature modules: resource and marketplace.
- Added native modules for core and proot to the project structure.
- Created a new wd.xml file to handle missing directory errors.
* Fix native-handle leaks, terminal lifecycle, and the build/run pipeline
Stability:
- Buffer/Snapshot Cleaners captured `this`, permanently defeating the
native-handle safety net (leaked a native snapshot every keystroke).
Capture only the Long handle, via static nativeCloseByHandle in
jni_buffer.cpp, so they actually fire; wrap transient snapshot reads
in .use{}.
- PtyProcess/VtParser: capture-free Cleaners; remove VtParser's banned
finalizer (+ static nativeCloseByHandle in jni_vt.c).
- pty.cpp: return -1 on EOF (was 0) so a finished shell's reader breaks
instead of spinning forever; TerminalSessionManager auto-reaps the
exited session, releases the foreground-service hold, and the UI drops
the tab. Make the session map thread-safe across the IO reader.
- TerminalView: reuse a char[] in onDraw (no per-cell String alloc) and
coalesce repaints to one per frame; guard draws against a closed parser.
- Fix a forward-referenced local fun that broke the build.
Build/run:
- ProjectRunner: templateId-first detection (no longer reports "no run
configuration" for a scaffolded project) with self-healing recipes that
scaffold/build a missing client live in the run terminal.
- Publish the ASP.NET server to ext4 and launch the managed DLL via the
dotnet host -- /workspace is a noexec FUSE mount, so the apphost cannot
be exec'd there. Version-agnostic target framework (newest installed SDK).
Verified on device (AYN Odin2): ASP.NET Core + React/Vite scaffold->build
->run renders in the browser at localhost:5080.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Hide non-functional destinations and trim over-described UI
- Hide the Search and SCM activity-bar destinations (no working UI yet)
and the disabled Problems / Debug Console right-panel tabs, via
available/enabled flags filtered at every render site (kept in the enums
for `when` exhaustiveness). A stale persisted tool selection now falls
back to Explorer.
- Drop the Output panel's needless "no PII" line; collapse the now-dead
disabled-tab color branches (also removes a sub-spec contrast value).
Verified on device: rail shows Explorer/Run/Extensions/SDK/Settings; the
right panel shows only Terminal/Output.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add a Stop control to the Run panel
Running a project left the server (dotnet/vite) running in its terminal
with no obvious way to stop it from the UI. The Run panel now shows
Stop + Re-run side by side while a run is active (building, or server up).
Stop (handleStopRun) sends Ctrl-C to the run terminal for a graceful
shutdown, cancels the server-ready poll, and resets run state; the run
terminal is kept so its output stays visible and Re-run can reuse it. A
run session that exits on its own now also clears run state.
Verified on device: Stop/Re-run appear during a run and Stop returns the
panel to Build & Run.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Disable npm progress bar in run recipes (was garbling the terminal)
npm's animated progress gauge (light-shade chars redrawn via carriage
returns) accumulated into a wall of dotted garbage in the run terminal
during `npm install`. Export npm_config_progress=false (plus fund/audit)
at the top of both run recipes so npm install/create emit clean,
line-by-line output.
Verified on device: the [3/5] npm install step now streams cleanly with
no progress-bar garbage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Run recipes from a script file instead of an inline heredoc
The run recipe was fed into the interactive shell as a `bash <<'JCRUN'`
heredoc, so bash echoed every line back with its `>` continuation prompt —
dumping the whole ~30-line recipe into the run terminal before any output
appeared. Write the recipe to <project>/.jcode/run.sh and invoke it with a
single `bash <path>` line instead (the script is read, not executed, so the
noexec /workspace mount is fine). The terminal now shows just the clean
[1/5]..[5/5] step echoes. Falls back to the inline heredoc if the script
cannot be written.
Pairs with the earlier npm_config_progress=false fix; together the run
terminal renders cleanly. Verified on device: no recipe dump, no progress-
bar garbage, build/run still serves at localhost:5080.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Fix editor font rendering ~2.3x too small (sp treated as px)
The editor Renderer set Paint.textSize directly from RenderConfig.fontSizeSp
(an sp value), but Paint.textSize is in pixels — so a 14sp font rendered at
14px, i.e. density times too small (about 2.3x on the 369dpi Odin2). Multiply
fontSizeSp by the display density (passed into Renderer) when computing the
glyph size, line-number size and line height. Apply the same conversion to
EditorView's tap hit-test and gutter-width math so taps still map to the
correct caret.
Build: green. Device visual verification pending (wireless ADB dropped).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Render terminal progress bars in place (handle CSI G / CHA)
The VT parser didn't implement CSI 'G' (Cursor Horizontal Absolute) — the
escape Node's readline.cursorTo(0) emits to return to column 0 before
redrawing a progress line in place. Without it the cursor never reset, so
npm's progress (and any CHA-based spinner/bar) marched down the screen into
a wall of dotted garbage. Add CHA ('G') and VPA ('d') handling so progress
redraws overwrite a single line; this fixes progress rendering for every CLI
(npm, git, agent tools), not just the run recipe. Re-enable npm's progress
bar in the run recipes now that it renders correctly (fund/audit stay off).
Verified on device: npm install shows a single in-place spinner/progress
line during Build & Run instead of garbage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Run projects in dev: separate Server + Client terminals
Build & Run now launches a development setup instead of a production
build-and-serve. For ASP.NET Core + Vite React it spawns two terminals,
started server-first:
- "Server": dotnet build -c Debug to ext4, then run the DLL with
ASPNETCORE_ENVIRONMENT=Development on :5080.
- "Client": Vite dev server (npm run dev) on :5173.
The browser opens the dev frontend (:5173). RunPlan now carries a list of
labelled RunTerminals; handleRun tears down the previous run's terminals,
spawns one per step, stops them together (Stop), and reaps them via the
exit listener. createSession takes a custom label, and the terminal tab bar
shows it (Server/Client) instead of "bash N".
Known limitation: the client runs from an ext4 staging copy (the FUSE
/workspace can't host node_modules), so HMR is on that copy, not live
/workspace edits - a follow-up can bind node_modules for true live HMR.
Verified on device: Server + Client tabs spawn server-first and the browser
opens the Vite dev server at localhost:5173.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Fix "multiple DataStores active" crash (UI-preferences singleton)
MainViewModel created the ui-preferences DataStore with
PreferenceDataStoreFactory.create per instance, and that factory's scope is
never cancelled. So when a second MainViewModel is constructed (e.g. after
the Activity is recreated returning from the browser on Build & Run), a
second DataStore for the same file is created and crashes with
"There are multiple DataStores active for the same file". Hold the store in
a process-singleton (UiPreferencesStore) created exactly once per process.
Verified on device: the action that reliably crashed before no longer does.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add long-press action menus to terminals
Content (long-press the terminal output) -> popup: Select text (arms a
drag-selection for the next touch), Select all (selects the visible screen
and copies), Paste (clipboard -> PTY, the first touch way to paste), Clear
(Ctrl-L). Replaces the old immediate long-press-drag selection.
Tab (long-press a session tab) -> dropdown: Rename, Clear (Ctrl-L to that
PTY), Close, Close others, Close all. Rename uses a dialog + an observable
label override (Session.label is now mutable) so the tab updates live.
Verified on device: both menus open with the right items.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Terminal: double-tap to type, single tap opens links/paths (configurable)
A single tap no longer raises the keyboard (it was too easy to trigger by
accident). New default behavior:
- Single tap: opens the token under the finger - a URL in the browser, or a
file path in the editor (resolves /workspace, absolute host, and
project-relative paths; strips a trailing :line). Non-link taps do nothing.
- Double tap: focuses and shows the keyboard, force-shown so it returns even
after a Back dismiss. Opening/switching a terminal now focuses without
auto-raising the keyboard.
- Long-press still opens the action menu; "Select text" still drag-selects.
Configurable in App Settings > Terminal > "Double-tap to type" (app-wide,
default on; off restores single-tap-to-type), stored in the ui-preferences
DataStore. The tap config reaches the deeply-nested terminal view via a
CompositionLocal to avoid prop-drilling through the sidebar layers.
Verified on device: single tap doesn't raise the keyboard; double tap does;
tapping an echoed URL opened the browser; the settings toggle is present.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Rename app "Preferences" -> "App Settings"
Renames the app-level settings destination and its page tab from
"Settings"/"Prefs" to "App Settings", so the name reads clearly against
project-specific "Project Settings". Updates the rail tooltip/a11y label,
the compact header chip, the settings page tab title, and a stale comment.
Verified on device: the destination opens an "App Settings" tab and the
breadcrumb reads ".../App Settings".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Don't auto-show the right drawer on launch
The right drawer's initial visibility seeded from `isLandscape`, so on a
landscape device it auto-opened (overlaying the editor) on every cold launch.
Seed it `false` instead, matching portrait, so launch never auto-opens the
drawer. The rememberSaveable keys are kept: a drawer the user opens still
survives Activity recreation, and rotation still re-seeds per the existing
design.
All open paths are unchanged - Run, the terminal/Show-Terminal toggle, and
the sidebar toggle still open it on demand. Background terminal sessions are
decoupled from drawer visibility (a session still auto-starts and simply
renders when the drawer is opened).
Verified on device: cold launch shows a full-width editor with no drawer;
the terminal toggle opens it and the prior session is restored.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* SDK Manager: compact layout
The SDK Manager is dense (13 entries x 3 buttons), so the default Material3
button size and ~16sp titles dominated the narrow side panel. Tighten it:
- Card/notice titles -> titleSmall; entry names + summary values -> bodyMedium.
- Shared CompactFilledButton/CompactOutlinedButton helpers (32dp min height,
12dp/4dp content padding, labelMedium text) replace the six default-sized
buttons (Refresh/Setup, Install/Verify/Remove, Show).
- Trim padding (12->10) and inter-element spacing across cards and rows.
- Shorten the verbose card/category/output descriptions to one line each.
Verified on device: titles, entry rows, and action buttons render compact
and legible in the side panel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add configurable theme bundles + swappable icon bundles
Foundation for a consistent, customizable look, selectable app-wide.
Design tokens (core/design):
- ThemeBundle: a named look = Material dark+light color schemes + matching
semantic colors (success/warning/info, which Material's scheme lacks) + an
optional font. Five built-ins: Catppuccin (default, the prior palette),
Dracula, Nord, Gruvbox, One Dark. Registry resolves by id, falls back to
default. The data shape is what a disk/asset bundle would deserialize into.
- JCodeSemanticColors + JCodeSpacing tokens, exposed via LocalSemanticColors /
LocalSpacing and a JCodeTheme accessor.
- IconBundle: JCodeIcon semantic slots resolved through the active bundle, with
per-slot overrides falling back to the built-in default, so a custom pack can
ship just its hero icons. Default "Material Rounded" bundle unifies icons to
the Rounded family. All ~30 call sites (JCodeShell, ExplorerView,
RunDebugPanel) now reference JCodeIcon via jcIcon() instead of Icons.* — the
whole icon set is swappable from one place.
Wiring:
- M3Theme takes a ThemeBundle + IconBundle and provides the locals.
- App-wide selection persisted in the ui-preferences DataStore
(theme_bundle_id / icon_bundle_id); resolved in MainActivity into M3Theme.
- App Settings gains "Theme bundle" (palette swatches) and "Icon bundle"
(icon preview) pickers.
Verified on device: switching theme bundle recolors the whole app live and
persists; icon refactor renders identically under the default bundle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add "J Code Line" custom vector icon bundle
A hand-authored minimal line icon set (~22 ImageVectors on a 24x24 grid:
folder, run, stop, terminal, search, settings-as-sliders, extensions-as-blocks,
sdk-as-cube, scm-branch, code, chevrons, etc.). Registered as a second icon
bundle; overrides its hero slots and falls back to the Material default for the
long tail. Selectable from App Settings > Icon bundle.
Init order matters: the icon map is declared before the bundle/list so
top-level initialization populates it first (an earlier ordering passed a null
overrides map and crashed at startup).
Verified on device: selecting it swaps the whole app's chrome to the custom
line icons live and persists across launches.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Browse/apply bundles in the Ext tab; use semantic success color
Extensions tab: below the project templates, add "Theme bundles" and
"Icon bundles" galleries (palette swatches / icon previews) with tap-to-apply
and an "Active" marker, mirroring the App Settings pickers. Threaded the
selection state + callbacks through WorkspacePanel -> ExtensionsPanel.
Consistency: Onboarding's log lines used a hardcoded green (Color(0xFF4CAF50))
that ignored the theme; replaced with JCodeTheme.semanticColors.success so it
tracks the active bundle.
Verified on device: the Ext galleries list all bundles, mark the active one,
and apply on tap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Install marketplace extensions at runtime; externalize templates
Templates are no longer bundled with the app. The marketplace and its extensions
now live in their own repos; the app fetches the index and installs extensions
on-device at runtime.
- ExtensionInstaller: fetches the marketplace index (marketplace.yaml) over HTTPS,
installs an extension by downloading its GitHub repo zip (codeload) and unpacking
it under filesDir/extensions/<id>/, and scans/uninstalls installed extensions.
No new deps (HttpURLConnection + ZipInputStream). Parses templates and the new
`language` pack manifest (completions / formatter / helpers).
- TemplateCatalog now aggregates templates from installed `templates` extensions
instead of bundled assets; removed the assets srcDir and the old asset loader
and deleted the in-repo marketplace/ directory (moved to j-code-ext-template-1).
- MainViewModel exposes installedExtensions / marketplaceEntries / busy + refresh,
install, and uninstall.
- Ext tab: a Marketplace section (Install) and an Installed section (Remove),
above the theme/icon bundle galleries.
Verified on device: the Ext tab fetched the live index (4 extensions); installing
"JCode Project Templates" and "C# Language Pack" downloaded + registered them, and
the installed templates populate the New-project dialog. Editor execution of
language packs (live completions/formatter) is a follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Pack-driven syntax highlighting + editor render fixes
Language packs now carry a syntax definition (keywords/types/strings/comment);
the editor colors the active file when a matching pack is installed.
- LanguagePack gains syntax + formatting fields; ExtensionInstaller parses them.
- SyntaxHighlighter: a small, dependency-free tokenizer producing ColoredSpans
(UTF-8 byte offsets) for comments/strings/numbers/keywords/types from the pack
rules. Wired in JCodeShell: for the active file, resolve the installed pack by
extension, tokenize on each edit (off-thread), and push a GLYPH_COLOR layer.
Editor render fixes (the coloring pipeline existed but was never driven):
- EditorView.onDraw now passes decorations to Renderer.draw (was defaulting to
EMPTY, so ColoredSpans never drew), and attach() observes the decorations and
caret flows to invalidate on change.
- Viewport.lineHeightPx (default 20, never updated) is now synced from the render
config, and a newly-attached EditorState seeds its viewport from the current
view size. Without these, only ~2 lines were considered visible on tab switch.
Verified on device: installing the TypeScript pack colors a .ts file (keywords
purple, types cyan, strings green, comments gray, numbers orange) and the full
file renders.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Editor long-press context menu with pack-gated language actions
Long-pressing in the editor selects the word under the finger and opens a
context menu. Always: Copy / Cut / Paste / Select all (working, via the
clipboard + EditTx). When the active file matches an installed language pack,
the menu is extended with Go to Definition, Find References, Rename Symbol, and
Format Selection.
- EditorView: GestureDetector long-press -> select word (offsetAt + wordAt) ->
onContextRequest(x, y, word); clipboard/selection helpers (copy/cut/paste/
selectAll); EditorContextRequest + EditorLanguageAction types.
- EditorViewHost: anchored DropdownMenu; clipboard items call the view, language
items (shown when languageActionsEnabled) call onLanguageAction.
- JCodeShell: enables language actions when the active file matches an installed
pack; semantic actions are deferred to a language server and currently show a
"coming soon" message (per the chosen approach).
Verified on device: long-press a .ts identifier shows the extended menu; a .yaml
file (no pack) shows only clipboard actions; tapping Go to Definition surfaces
the deferred-LSP message.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Extension versioning: show updates + Update action
The marketplace index now carries each extension's latest version; the app
compares it against the installed copy's version.
- MarketplaceEntry gains version; ExtensionInstaller parses it; compareVersions
/ isUpdateAvailable helpers (dotted, numeric).
- Ext tab: each row shows its version; an installed-but-outdated extension shows
"Update" (re-downloads the latest) instead of "Installed", and the Installed
section shows "vX -> vY". Up-to-date shows "Installed"; missing shows "Install".
Verified on device: bumping the C# pack to v0.2.0 (repo + index) surfaced an
Update on the v0.1.0 install; tapping Update pulled v0.2.0 and the row flipped
to Installed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Settings: search filter + section grouping
App Settings now opens with a "Search settings" field and groups the cards under
section headers (Overview / Appearance / Environment / Editor / Terminal / Files).
- LocalSettingsQuery composition local holds the query; SettingsCard self-hides
when the query matches neither its title, description, nor optional keywords.
- SettingsSectionHeader renders the group label, and hides while searching so
results read as a flat filtered list.
Verified on device: headers show when idle; typing "terminal" narrows to the
Terminal card only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Extension taxonomy + dependency suggestions on install
Extensions can now declare category/subcategory and require/suggest other
extensions, SDKs, and LSPs; the marketplace index carries them.
- MarketplaceEntry gains category/subcategory + requires/suggests (ExtensionDeps:
sdks/lsps/extensions); ExtensionInstaller parses them. ExtensionType adds
Formatter (for future formatter extensions).
- Ext tab rows show "type · Category/Subcategory · vX".
- Installing an extension with deps opens a non-blocking dialog with Required and
Suggested groups: other-extension deps install inline (or show "Installed");
SDK/LSP deps are listed (SDK Manager / language-server). "Install <name>"
proceeds regardless.
- marketplace.yaml: template pack suggests the C#/TS/JS packs (+ dotnet/nodejs
SDKs); language packs require their SDK and suggest their language server.
Verified on device: rows show categories; installing JCode Project Templates
shows a Suggested dialog (C#/TypeScript = Installed, JavaScript = Install).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Settings: Formatter selection (built-in + future formatter extensions)
Adds an app-wide Formatter choice (ui-preferences "formatter_id", default
"builtin"). App Settings > Editor shows a "Formatter" card listing Built-in plus
any installed type:formatter extensions, so installing a formatter extension
later adds it to the list automatically.
Verified on device: the Formatter card shows Built-in selected and is findable
via the settings search ("format").
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Refactor: extract workbench dialogs out of JCodeShell
Move the New/OpenFolderType/Scaffold dialogs (and their TypeOption/TemplateOption
helpers) from the 3449-line JCodeShell.kt into dev.jcode.workbench.dialog.
WorkbenchDialogs.kt. Pure code movement, no behavior change; the two dialogs
called from JCodeApp are internal, the rest file-private. JCodeShell -> 3151 lines.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Refactor: extract Extensions/marketplace UI out of JCodeShell
Move ExtensionsPanel + the dependency dialog + bundle gallery rows into
dev.jcode.workbench.marketplace.ExtensionsPanel.kt (ExtensionsPanel internal,
the rest file-private). Pure code movement. JCodeShell -> 2840 lines.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Refactor: extract workbench model/enums out of JCodeShell
Move TerminalTapConfig, LocalTerminalTapConfig, WorkbenchTool, RightPanelTab into
dev.jcode.workbench.WorkbenchModel.kt (internal). Shared types now live in one
place for the rest of the JCodeShell split to import. JCodeShell -> 2808 lines.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Rework Environment setup: dialog -> in-editor page + readable distro list
The cramped Environment setup dialog squished long distro labels into
unreadable vertical character columns. Convert it to a full-width
in-editor page tab and replace the segmented-button distro picker with
a vertical RadioButton list (fixes both the page and the first-run
screen, which share StepperScreen).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add LSP Manager: install/verify/remove language servers per distro
A new "LSP Manager" rail tool mirrors the SDK Manager: it installs,
verifies, and removes language servers inside the active distro, tracks
installed state per distro (DataStore), and streams command output.
- core:distro is the single source of truth (LspServerCatalog) for the
7 built-in servers (clangd, typescript-language-server, csharp-ls,
pyright, gopls, rust-analyzer, kotlin-language-server) including a new
C# server. The install/verify/uninstall machinery lives in
DistroService, reusing its mutex + execInDistro + DataStore.
- core:lsp's LspServerDescriptor.BUILT_IN now derives from that catalog
(core:lsp -> core:distro) so the runtime client and the manager never
drift. Avoids the distro -> lsp -> term -> distro cycle.
- feature:lsp-manager renders the compact panel; wired through
MainViewModel + JCodeShell + a new JCodeIcon.Lsp.
Verified on device: panel lists all 7 servers grouped by category with
per-distro install state; Verify runs the real command in the distro
(exit code + stderr captured, status + rolling log updated).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Redesign SDK & LSP managers: compact rows + detail pages + status checks
The drawer panels are now dense, tappable rows (name, one-line
description, status chip) instead of inline buttons. Tapping a row opens
a full in-editor detail page (title, description, Install/Update/
Uninstall + Verify + rolling output).
- Status is checked live when a manager comes into view: a full
installed + update-available check runs async in the background
(guarded against re-entry), surfacing a "Checking…" state and flipping
rows to Installed / Update available as results arrive.
- "Update available" is detected per entry via an optional updateCheck
(apt list --upgradable for apt packages; npm outdated for npm globals);
entries without a reliable check just show Installed.
- Shared manager UI (rows, status chip, section card, detail screen)
extracted to core/design ManagerUi.kt and used by both managers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Redesign Extension Manager: compact rows + detail page with samples
The Extensions panel now mirrors the SDK/LSP managers: dense rows
(name, one-line description, status chip: Installed / Update available /
Not installed) that open a full in-editor detail page. The detail page
shows title, description, a samples section, a requirements section
(required/suggested SDKs, LSPs, and extensions with install state), and
Install / Update / Uninstall actions (the dependency prompt moved here).
Theme and icon bundle galleries stay in the panel.
- MarketplaceEntry/InstalledExtension gain description, longDescription,
and samples (List<CodeSample>); the index + manifest parsers read them.
- The shared ManagerDetailScreen gains showVerify/showOutput flags
(extensions hide both) and is now vertically scrollable so tall detail
pages reach their actions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Rename Settings, slim SDK/LSP panels, system-back nav, fix verifier limit
- Rename "App Settings" -> "Settings" (rail tool + page tab); the Global
and Project/Workspace scopes already live on that screen.
- Remove the environment-config block (distro/status + "Environment
setup") from the SDK and LSP manager panels; environment config stays
on the Settings screen, which already hosts it. Panels keep just the
installed count + Refresh, and point to Settings when setup is needed.
- Detail pages are one-per-type now: opening an SDK/LSP/Extension detail
reuses a single tab of that kind instead of stacking new tabs.
- System back navigates back a step (close the active page/detail tab,
then modal drawer, then right sidebar). With nothing left, a second
back within 2s exits — or, if a run is in progress or a terminal is
live, backgrounds the app so the work keeps running.
- Fix a launch-time VerifyError: JCodeShell had so many parameters that
ART's verifier rejected the method (register overflow). Bundle the 14
SDK/LSP/Extension manager callbacks into WorkbenchManagerActions to cut
the param count, and extract the back-handling into WorkbenchBackHandler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Build & Run: per-project config in .jcode/run.yaml + multi-project panel
The Run panel now lists projects instead of just the open one: every
project in a User Workspace gets a row, while the Default Workspace shows
the single open project. Each row shows the run kind + a serve URL and
offers Build & Run and Configure.
- New per-project config at /{project}/.jcode/run.yaml (RunConfig:
name, readyPort, terminals[label+command]), read/written by
RunConfigStore (core:config). Optional run scripts already live at
.jcode/run-<label>.sh.
- ProjectRunner.effectivePlan prefers a saved run.yaml, else falls back
to template/filesystem detection. editableRunConfig seeds the editor
from the detected plan (or a blank starter for unrecognized projects).
- Configure opens a structured in-editor page (EditorPageKind.RunConfig):
name, ready port (blank = no browser), and an add/remove list of
terminals with editable bash commands; Save writes run.yaml.
- handleRun is now per-project; readyPort 0 runs terminals without
opening a browser. The top-bar run button runs the selected project.
Verified on device: Default Workspace shows the open project; Configure
pre-fills from the detected plan; Save writes .jcode/run.yaml; the panel
then reads the config back. No verifier crash (manager callbacks stay
bundled; +3 run params are well under the limit).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Redesign Build & Run panel: action-first compact rows
Replace the chunky per-project card (two big buttons, a wrapping label)
with a dense single row per project: name + a status chip (Idle /
Building… / Running / Not set up) on top, "<kind> · :<port>" beneath,
and inline controls — Run (▶) when idle, Stop (■) + Open-in-browser (↗)
while running, plus a Configure (gear) action. One tap to run, far less
vertical space, consistent with the redesigned SDK/LSP/Extension panels.
Verified on device: Idle row -> tap Run -> row shows Building… + Stop +
Open-in-browser -> tap Stop -> back to Idle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Fix editor: working soft keyboard + correct text input
The editor was unusable: the soft keyboard never opened, and once it did
(after wiring onCheckIsTextEditor + showSoftInput) every keystroke corrupted
the buffer. Root cause was twofold:
- The native piece-tree (native/buffer/piece_tree.cpp) is an unfinished
skeleton — its insert/split orphans the inserted text and leaves the
original piece in place, duplicating the whole document on the first edit;
red-black balancing and remove are empty stubs. It had never been exercised
on-device because input never worked. Fall back to the correct pure-Kotlin
array-splice buffer via USE_NATIVE_BUFFER=false until the tree is fixed.
- The IME path desynced: initialSel was hardcoded to 0, the caret was never
reported back, and composing edits ran against Gboard's stale cursor model.
Report the real caret in onCreateInputConnection, restartInput on tap,
updateSelection after every edit, and disable composing/autocorrect
(VISIBLE_PASSWORD) so each keystroke commits directly — the right model for
a code editor. Anchor commit/compose to the composing-region start and
advance the caret per newCursorPosition.
- Editing keys arrive as key events, not commitText, so handle Backspace,
Forward-delete, Enter, Tab, arrows, and hardware-keyboard characters in
dispatchKeyEvent (UTF-8-aware caret stepping).
Verified on device (Odin2): type, backspace, Enter, and tap-to-reposition all
produce a correct buffer with correct rendering.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add configurable symbol bar + hide-suggestions toggle for the editor
The IME's suggestion/autocorrect strip is useless for code, and a touch
keyboard makes brackets/punctuation slow to reach. Add two independent,
on-by-default editor settings plus a quick-insert symbol bar.
- :core:design SymbolBar.kt: a horizontally-scrollable bar of code keys
(Tab + configurable symbols + caret arrows) that sits above the keyboard.
Keys use detectTapGestures (not clickable) so a tap never steals focus and
dismisses the keyboard. Also EditorKeyboardSettings — a single holder for the
two flags + key list + onChange, so JCodeShell gains only one parameter
(it sits near the ART verifier's register limit; same mitigation as
WorkbenchManagerActions).
- :core:editor EditorView: suppressSuggestions now drives the IME input type
(VISIBLE_PASSWORD/NO_SUGGESTIONS when on, plain multi-line text when off) and
restarts input when flipped while focused. Exposes insertTextAtCaret /
insertIndent / moveCaretBy so the symbol bar can drive the active editor.
- :app MainViewModel: three DataStore prefs (editor_hide_suggestions,
editor_show_symbol_bar default true; editor_symbol_keys newline-delimited).
- :feature:settings: a "Soft keyboard" card with the two toggles and a chip
editor (add / remove / reset) for the symbol keys.
- :feature:editor-pane: renders the bar at the bottom of the pane when a file
editor is open with the keyboard up, wired to the active EditorView.
Verified on device (Odin2): bar shows/hides per toggle, inserts symbols at the
caret without dismissing the keyboard, Tab/arrows work, hide-suggestions flips
the IME strip, and the key list persists.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Replace soft-keyboard options with a full in-app keyboard
There's no reliable way to suppress Gboard's toolbar/suggestion strip from an
app, so instead own the keyboard entirely. A single "Use in-app keyboard"
setting (on by default) replaces the previous hide-suggestions / show-symbol-bar
toggles.
- :core:design InAppKeyboard.kt: a self-contained on-screen code keyboard —
QWERTY with one-shot Shift, a ?123 numbers/symbols layer, a configurable
code-symbol row (Tab + symbols + ←↓↑→ + a ⌄ hide key), and a bottom row
(?123/ABC, comma, space, period, Enter). Backspace fires once on press and
auto-repeats while held. Keys use pointer gestures (never clickable) so taps
don't steal focus from the editor. Layout is data-light so extensions can
supply fuller custom layouts later. EditorKeyboardSettings is the single
host param (mirrors the WorkbenchManagerActions verifier mitigation).
- :core:editor EditorView: useInAppKeyboard suppresses the system IME
(onCheckIsTextEditor returns false, taps request the in-app keyboard instead
of showSoftInput). Input arrives through public insertTextAtCaret / insertIndent
/ backspace / moveCaretBy / moveCaretLineBy. The system-keyboard path keeps the
VISIBLE_PASSWORD/no-compose input type for when the feature is turned off.
- :app MainViewModel: editor_use_inapp_keyboard (default true) + editor_code_keys
(configurable code row) DataStore prefs.
- :feature:settings: one "Use in-app keyboard" toggle + the code-symbol-row
chip editor (add / remove / reset).
- :feature:editor-pane: hosts InAppKeyboard at the bottom of the pane, tracks its
own visibility (no IME insets to key off), and maps key actions to the active
EditorView. Removes the old SymbolBar.
Verified on device (Odin2): default-on shows the in-app keyboard (no Gboard);
letters, code symbols, Shift, ?123, Tab, Space, Enter, arrows, single +
hold-repeat Backspace, and Hide all work and keep the buffer correct; toggling
the setting off restores the system keyboard.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Make the in-app keyboard an app-wide bottom surface
The keyboard was nested inside the editor pane, so it was only as wide as that
pane (the left rail sat beside it) and tied to the editor's layout. Lift it to
the app root as a single full-width surface driven by a shared controller.
- :core:design: add InAppKeyboardController (visible + a KeyActionSink) exposed
via LocalInAppKeyboard. An input target registers its sink and calls show() on
focus; the controller's dispatchKey() routes keys to it (and handles Hide).
Editor today; terminals/fields can plug into the same controller later.
- :app JCodeShell: render one InAppKeyboard at the root, pinned full-width to the
bottom, shown when the setting is on and the controller is visible. Its measured
height pads the content above so the caret stays visible (content resizes; the
keyboard isn't a floating overlay). Provided via LocalInAppKeyboard.
- :feature:editor-pane: EditorViewHost now registers a KeyActionSink (mapping
KeyAction -> the active EditorView) and calls controller.show() on tap /
hide() on release; the per-pane keyboard rendering + lifted view state are gone.
Verified on device (Odin2): tapping the editor raises a full-width keyboard at the
app bottom; the editor + status bar resize above it; typing, backspace and the ⌄
hide key all work; no VerifyError.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Hide the in-app keyboard when a physical text keyboard is attached
When a hardware keyboard is connected there's no reason to show the on-screen
keyboard — hardware keys already reach the editor via EditorView.dispatchKeyEvent.
JCodeWindowInfo.hasPhysicalKeyboard is too loose for this: it counts any
non-virtual device with keys, which includes game controllers (the Odin2's built-in
Odin Controller reports KEYBOARD_TYPE_NON_ALPHABETIC), so gating on it would hide
the keyboard with no real keyboard attached. Add a stricter hasTextKeyboard
(configuration.keyboard == QWERTY and not hidden, or a non-virtual input device with
KEYBOARD_TYPE_ALPHABETIC) and gate the app-wide in-app keyboard on
!windowInfo.hasTextKeyboard. It updates reactively via the existing
InputManager.InputDeviceListener hot-plug path.
Verified on device (Odin2): with only the built-in controller, hasTextKeyboard is
false and the in-app keyboard still shows (no game-controller false-positive).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Remove the in-app keyboard and its settings
The in-app keyboard didn't reach a good enough bar; using a dedicated code IME
(e.g. Hacker's Keyboard) is the better path. Remove it entirely and let the editor
use the system keyboard.
- Delete core/design/InAppKeyboard.kt (keyboard, controller, sink, CompositionLocal,
EditorKeyboardSettings, KeyAction).
- EditorView: drop useInAppKeyboard / onInAppKeyboardRequested and the public
insert/caret methods that only the in-app keyboard used; onCheckIsTextEditor is
true again and a tap always raises the system IME. Keep the VISIBLE_PASSWORD /
no-suggestions / no-compose input type so any system IME (incl. Hacker's
Keyboard) commits each keystroke directly without corrupting the buffer.
- JCodeShell: remove the root-level keyboard surface, the InAppKeyboardController,
the content padding, and the editorKeyboard threading; the root is a plain
imePadding Box again.
- MainViewModel: drop the editor_use_inapp_keyboard / editor_code_keys prefs.
- feature/settings: remove the "Soft keyboard" card + symbol-row editor.
- feature/editor-pane: EditorViewHost no longer wires a key sink.
- core/adaptive: drop the now-unused hasTextKeyboard from JCodeWindowInfo.
Verified on device (Odin2): tapping the editor raises the system keyboard and
typing edits the buffer correctly; no in-app keyboard, no Soft keyboard settings,
no VerifyError.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Remove the minified left rail
The icon-only left rail (DockRail) duplicated the tool switcher that already
lives in the left sidebar header (WorkspaceHeader's SidebarToolButton row), so it
was redundant. Remove it; the editor now uses the full width and tools are
reached via the ≡ button → the sidebar's Files/Run/Ext/SDK/LSP switcher.
- JCodeShell: drop the DockRail render, the DockRail + RailButton composables,
orderedRailTools, the railTools/showPersistentRail locals, and the
railToolOrder/onReorderRail params; prune the now-unused tooltip + semantics
imports.
- MainViewModel: drop the rail_tool_order preference and setRailToolOrder (only
the rail used it; the sidebar switcher uses the fixed WorkbenchTool order).
Verified on device (Odin2): no left rail, editor spans full width, the ≡ button
opens the sidebar with its tool switcher + file explorer; no VerifyError.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add editor Save: Ctrl+S, top-bar button, dirty tracking, write-through
Persist the active editor tab's buffer to its file via the Fs API.
- EditorState: dirty StateFlow (set on applyEdit) + markClean()
- EditorView: onSaveRequest + Ctrl+S in dispatchKeyEvent
- EditorPane: thread onSave through to the EditorView
- MainViewModel: saveActiveTab/saveTab write snapshot bytes via LocalFs;
clear dirty only if the snapshot is unchanged; reflect EditorState.dirty
onto EditorTab.isDirty for the tab dot
- JCodeShell: Save button in the editor top bar (highlighted when dirty)
- IconBundle: Save icon slot
Device-verified on Odin2: edit -> dirty dot -> save -> persists across reopen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Tighten Settings typography + compact search field
Card titles and several row labels (Toggle/Stepper/Option/Summary/Warning) used
the default bodyLarge (16sp), inconsistent with the 14sp scale used elsewhere
(section headers, bundle rows). Pin them all to bodyMedium so the screen reads as
one consistent scale: section header (primary 14sp) > card title (14sp semibold) >
row label/value (14sp) > description/supporting (12sp).
Replace the tall default OutlinedTextField search box with a compact
SettingsSearchField: a ~40dp rounded surface with a leading search icon,
bodySmall placeholder/input via BasicTextField, and an × clear button when
non-empty.
Verified on device (Odin2): search is smaller, typing filters cards (e.g.
"theme" → Theme bundle only), × clears it; card titles and values are no longer
oversized.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Trim built-in themes to three
Keep Catppuccin (default), Dracula, and Nord; drop Gruvbox and One Dark.
ThemeBundleRegistry.byId() falls back to the default for an unknown id, so a
previously-persisted gruvbox/one-dark selection degrades gracefully to Catppuccin.
Verified on device (Odin2): the Theme bundle picker lists exactly three themes
and selection still works.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add "hide status bar with keyboard" setting
A new app-wide toggle (Settings → Appearance → Immersive keyboard, off by default)
hides the system status bar while the soft keyboard is open, reclaiming vertical
space for the editor and terminal — useful in landscape on small screens. The bar
returns when the keyboard closes and can be swiped down to reveal while hidden.
- MainViewModel: hide_status_bar_with_keyboard pref (default false) + setter.
- JCodeShell: StatusBarKeyboardController watches WindowInsets.isImeVisible and
drives the WindowInsetsController (hide/show statusBars, transient-by-swipe
behavior); restores the bar on dispose. Wired into JCodeApp; the toggle threads
to SettingsFeature.
- feature/settings: "Immersive keyboard" card with the toggle.
Verified on device (Odin2): with the toggle on, focusing the editor hides the
status bar (top bar slides to the top edge) and dismissing the keyboard restores
it; off by default leaves the bar visible. Works for any soft keyboard, so the
terminal is covered too.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Remove the Overview section from Settings
The "Overview" section (the read-only "Effective config" summary card) duplicated
values the editor and the editable cards below already show, so drop it. Settings
now opens straight to Appearance.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Open + focus a file at line:col tapped in the terminal
Tapping a path in the terminal already opened the file, but stripped any
`:line` / `:line:col` suffix. Now it parses them (1-based, as compilers/grep
emit) and focuses the editor there.
- EditorState: a RevealRequest (0-based line/col) that the view consumes once it's
laid out with correct metrics (a fresh tab has no viewport line height yet);
requestReveal/clearReveal.
- EditorView: on a reveal request, place the caret at the line/col, scroll it a
couple of lines below the top (context above), and focus the view. Line height
is computed from the render config so it's correct even before first layout.
- MainViewModel: openFileByGuestPath parses the optional :line:col, threads it to
openLocalFile -> requestReveal (works for an already-open tab too), and emits
bringEditorToFront.
- JCodeShell: collects bringEditorToFront and, in modal layouts (e.g. mobile
landscape where the terminal is a drawer over the editor), closes the terminal
so the editor is visible.
Verified on device (Odin2, mobile landscape): tapping
/workspace/runtes/package.json:10:3 opened package.json, closed the terminal
drawer, scrolled to line 10, and put the caret at 10:3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add terminal `code`/`jcode` command to open files in the editor
A guest `code <path>[:line[:col]]` (alias `jcode`) command now opens and
focuses a file in the editor, like VS Code's `code` CLI. The command is a
shell function installed into the rootfs at /etc/profile.d/jcode-open.sh
that prints a custom OSC 7711 escape with the (PWD-resolved) path. The
session reader scans the PTY output for that escape and routes the path
token to the existing openFileByGuestPath, reusing line:col reveal and
bring-to-front. The VT parser consumes the unknown OSC without printing.
Verified on device: absolute + relative paths, existing-tab focus +
new-tab open, line:col jump, terminal auto-closes, no stray output.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Name terminal tabs after the running program
Terminal tabs are now auto-named from the foreground program instead of a
static "bash N" + manual rename. A bash hook (installed via the existing
/etc/profile.d shell-integration script) emits OSC 7712 with the running
command's basename on each command and "terminal" at the prompt; the
session reader scans for it and updates the tab label live. Default is
"terminal".
The OSC 7711 open-file scanner is generalized into a single OscScanner that
reports any (code, payload); the reader routes 7711 -> open file and
7712 -> tab title. Removed the static default and the manual Rename
dialog/labelOverrides.
Verified on device: default "terminal"; running `sleep 8` shows "sleep" then
reverts; `code <file>` open-file still works. Run-pipeline tabs auto-name to
the invoked wrapper ("bash") since run steps are shell scripts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Name run-pipeline tabs after the tool, not the bash wrapper
Run terminals invoke each step as `bash run-*.sh`, so the interactive
tab-title hook only saw "bash". Set BASH_ENV to the shell-integration
script so non-interactive bash sources it too: the DEBUG trap then fires
for the script's own commands, naming the tab after the actual tool
(npm/vite/dotnet) — the outcome of the foreground-process option, without
native /proc polling or its Android SELinux risk.
Guarded the title hook with `[ -t 1 ]` so it stays silent when stdout is
captured/piped (e.g. $(bash -c ...)), preventing OSC bytes from leaking
into captured output.
Verified on device: a Run client tab shows "npm" (was "bash") while
serving Vite on :5173, with clean output.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Unify long-press context menus into one compact component
Every long-press context menu now uses a single CompactContextMenu
(core:design): a top row of icon-only buttons for common verbs
(copy/cut/paste/rename/delete/close) plus compact icon+label list items
for the rest. Replaces six separate, inconsistent, text-only menus.
- New CompactContextMenu + ContextAction in core:design; added menu icons
(copy, cut, delete, open, rename, select-all, clear, definition,
references, format) to the JCodeIcon set.
- Migrated: Explorer file/folder, project list, workspace header, terminal
tab, and editor menus.
- TerminalView's native PopupMenu now routes its long-press to the host via
onContextMenu, so it renders the same Compose menu (mirrors EditorView).
Verified on device: all six menus show the hybrid layout consistently,
positioned correctly (anchored and at-touch-point), destructive actions in
the error color.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Make the right-drawer Hide button icon-only + add tooltips to icon buttons
The right-drawer "Hide" text button is now an icon-only ChevronRight
button. To keep icon-only controls discoverable, add a reusable JcTooltip
(core:design) wrapping Material3 TooltipBox — its label shows on long-press
(touch) or hover (pointer).
Tooltips applied to every icon-only button, mostly via the shared helpers
(WorkbenchIconActionButton, ToolbarIcon, the CompactContextMenu quick-action
row) plus the inline ones (project/file MoreVert, breadcrumb up, editor and
terminal tab +/x, RunDebug open/stop/run/configure). Existing
contentDescriptions are reused as the tooltip text.
Verified on device: Hide renders as an icon; long-pressing it (and a
context-menu quick action) shows the title tooltip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add "Hide tab close button" setting (default off)
A new Tabs setting hides the "×" on editor and terminal tabs to avoid
accidental closes. Editor tabs gain a long-press → Close menu (terminal
tabs already had one), so a tab is never stranded when the × is hidden.
The flag is shared via a CompositionLocal (LocalTabCloseButtonSetting,
carrying both value and setter) consumed by the deep tab UIs and the
settings toggle — deliberately NOT threaded as JCodeShell params: that
composable is at the ART verifier's register limit and two more params
tipped it into a launch-time VerifyError. The local carries the setter too
so the settings screen reads it without new params.
Verified on device: launches cleanly; toggling on live-removes the × from
editor + terminal tabs; long-press → Close still closes a tab.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add Save-button long-press pop-over (Undo/Redo/Discard/Save all)
Long-pressing the top-bar Save button opens a compact icon-only pop-over
with Undo, Redo, Discard (revert to disk), and Save all. Actions are
carried via a LocalEditorSaveActions CompositionLocal rather than new
JCodeShell params, which would trip the ART verifier register limit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Show a round dot on editor tabs with unsaved changes
The modified indicator was a small square before the title; make it a
round dot in the trailing slot (taking the "×" spot, editor-style) so an
unsaved tab reads clearly. The "×" still shows for clean, closeable tabs;
closing is done from the tab's long-press menu.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Auto-reload clean editor tabs when the file changes on disk
A clean (non-dirty) tab now mirrors its file: when an external writer
(e.g. an agent in the terminal) changes it, the editor reloads it. A
dirty tab is never clobbered. Detection is by a (lastModified, size)
signature polled on foreground-regain, a slow RESUMED tick, and tab
switch — not FileObserver, since proot/terminal writes are in another
mount namespace and don't fire the app's inotify.
Reload goes through a new atomic EditorState.replaceAll(text, onlyIfClean)
that runs as one transaction on the single-writer dispatcher, so a
concurrent keystroke can't interleave or be clobbered. The signature is
updated on save/discard so our own writes never self-trigger; a post-read
stability check defers reloads while the file is still being written; and
the reload notice is throttled.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Top-bar action buttons: Run/Stop, debug + terminal popovers, shimmer/badge
- Reorder top-bar actions to Save - Run - Terminal (then right-drawer toggle).
- Run toggles to Stop while a run is active (wired to handleRun/handleStopRun).
- Run long-press opens a debug pop-over: Rerun (re-invokes the run); Continue
and Step Into/Over/Out are shown but disabled (no debug engine yet).
- Terminal button shimmers while any session has a foreground process (OSC-7712
title != "terminal"), and shows a dot badge for new background instances
(cleared once the terminal panel is viewed).
- Terminal long-press lists live instances; picking one opens the right drawer
focused on that session.
ContextAction gains an `enabled` flag; CompactContextMenu greys/disables it.
WorkbenchIconActionButton gains optional shimmer + badge. All new wiring threads
through EditorWorkspace/WorkbenchTopBar (no new JCodeShell params).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Cap terminal tab labels to 8 chars; harden top-bar review findings
- Terminal session tabs (right drawer) and the Terminal-button instance list
now cap each label to 8 characters (e.g. "ASPNETCORE_ENVIRONMENT=Development"
-> "ASPNETC…") so a long OSC command title can't stretch a tab.
Review fixes for the top-bar change:
- WorkbenchIconActionButton: create the shimmer InfiniteTransition
unconditionally (Rules of Composition); its value is only read when shimmer
is on, so idle buttons still don't recompose per frame.
- Compute terminalBusy/terminalHasUnseen/terminalInstances via derivedStateOf
so a busy-title update doesn't rebuild the top bar or churn a new list/frame.
- Prune seenTerminalIds to live sessions in the tracking LaunchedEffect so it
can't grow stale across terminal close / run teardown.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Terminal: handle CNL/CPL (ESC[E / ESC[F) so live output redraws in place
The native VT parser implemented cursor up/down (A/B) but not Cursor Next
Line (E) or Cursor Previous Line (F). The .NET build / MSBuild terminal
logger uses ESC[<n>F to return to the top of its live progress block before
redrawing; with F unhandled the cursor never moved up, so each tick's
duration ("(0.1s)", "(0.2s)", ...) landed on a new line instead of updating
in place. Add E (down n, col 0) and F (up n, col 0), clamped to the scroll
region like A/B. Device-verified with `dotnet build` (net10): the duration
now updates on a single line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* App auto-versioning: VERSION.txt (1.0.0) + git-commit-count versionCode
versionName now reads /VERSION.txt (semver source of truth, start 1.0.0);
versionCode is the git commit count (monotonic, deterministic, offline). Both
fall back safely when VERSION.txt or git is unavailable. Enable buildConfig so
BuildConfig.VERSION_NAME exposes the running version for extension
min/targetJCodeVersion compatibility checks (used by the .jext loader). No CI.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Install extensions only from verified .jext packages
The marketplace + installer now use the .jext model instead of cloning
extension repos:
- fetchIndex parses the new marketplace.yaml schema (uniqueName, jext path,
fingerprint, min/targetJCodeVersion); only entries that ship a .jext are
listed (load-only-.jext).
- install(entry, appVersion) downloads the entry's .jext, verifies it
(.jext-manifest.json per-file sha256 + the order-independent package
fingerprint, plus the index fingerprint), checks minJCodeVersion against the
running app version, then extracts (files at the zip root — no top-dir strip)
to filesDir/extensions/<uniqueName>/ and parses extension.jehm + extension.yaml.
- Add installLocalJext(file) for sideloading. Drop the codeload repo-clone path.
- MarketplaceEntry: drop repo; add jext/fingerprint/min/targetJCodeVersion.
- MainViewModel passes BuildConfig.VERSION_NAME for the compatibility check.
Device-verified end-to-end on Odin2: browse -> download .jext -> verify ->
min-version check -> install -> open hello.js -> "lang: JavaScript" + highlighting.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Editor: extend syntax highlighting to functions, variables, constants, properties, operators, annotations
TokenPalette grows from 5 to 11 token classes. The tokenizer now classifies
identifiers as keyword / type / function-call / constant / property / variable
(via call-paren and member-dot lookahead and an ALL_CAPS constant heuristic),
and colors operators and @annotations. Pure app-side change driven by each
LanguagePack's existing rules, so it benefits every installed language pack.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Extensions: show marketplace/UI icons with a dependency-free loader + monogram fallback
MarketplaceEntry carries the marketplace-published icon URL (dist/icons/<id>.png),
InstalledExtension resolves its shipped icon file from the extracted package.
ManagerListRow/ManagerDetailScreen gain an additive optional leading slot (SDK/LSP
managers unaffected). A new ExtensionIcon composable decodes the PNG off-thread via
BitmapFactory (local file for installed, cached remote fetch for marketplace) and
falls back to a type-tinted monogram when no asset is present, so every row always
shows an icon.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Editor: make the language-pack formatter real (Format Document action)
The "Format" action no longer shows "coming soon". A new dependency-free
CodeFormatter applies the active language pack's basic rules — trim trailing
whitespace, convert leading tabs to `indent` spaces, ensure a single final
newline (LF/CRLF preserved). MainViewModel.formatActiveTab() resolves the
language pack for the focused tab and applies the result as one undoable
applyEdit. Wired via the existing LocalEditorSaveActions CompositionLocal
(no new JCodeShell param, per the ART verifier limit). EditorLanguageAction
FormatSelection -> FormatDocument; the semantic actions still require an LSP.
Device-verified: tab->spaces, trailing trim, and final-newline insertion all
applied to a JavaScript file via the editor context menu.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Editor: real as-you-type completions + snippet helpers from language packs
Wires the orphaned core/editor-completion framework into the editor. EditorView
now computes the identifier prefix at the caret after every edit/caret move and
emits a CompletionAnchor (prefix + byte range + pixel position); a generic
replaceRange() accepts a chosen item. EditorViewHost overlays CompletionWindow,
sourcing items via the new LocalCompletionSource CompositionLocal, which the app
fills from the focused file's language pack (completions, helpers, keywords,
types) through languagePackCompletionItems(). Pack completions and helpers carry
LSP snippet syntax, expanded via SnippetEngine — whose tab-stop offsets are now
recorded during parse (the old indexOf-based apply mislocated empty stops like
$0). CompletionWindow finally honors its anchor coordinates via a
PopupPositionProvider (flips above when there's no room below).
Device-verified: typing/placing the caret after "c" in a .js file pops keywords
+ the "cl" console.log snippet; accepting expands "console.log()" with the caret
placed inside the parens ($0).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Editor: fix three review findings in the formatter + completions
- CodeFormatter no longer collapses an all-whitespace file to empty: with
insertFinalNewline it now always re-adds exactly one trailing newline.
- SnippetEngine.apply converts each tab stop's recorded CHARACTER index to a
UTF-8 BYTE offset before shifting by the (byte) insertion offset, so caret
placement is correct for non-ASCII snippets (not just ASCII).
- EditorView.replaceRange keeps suppressAnchorUpdate set through the whole
sync (updateImeCursor + restartInput + dismiss) so accepting a completion
can never transiently re-open the popup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* App: apply the JCode launcher icon
Replaces the default system icon with the JCode logo. Adds legacy mipmaps
(ic_launcher / ic_launcher_round at all densities) plus an adaptive icon
(ic_launcher_foreground + a #151922 background color) so it renders cleanly
under any launcher mask. Manifest now points android:icon/roundIcon at
@mipmap/ic_launcher. Device-verified in the app drawer (circular mask).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Extensions: show the publisher/author (channel) in the marketplace + UI
MarketplaceEntry and InstalledExtension carry an `author`, read from the
index `publisher` (remote) and each extension.yaml `publisher` (installed).
The detail header subtitle leads with "by <author>", and the marketplace /
installed list rows prefix their description with it. Device-verified: the C#
pack shows "by jcode" on its detail page.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* App: dark themed splash screen (no more white flash)
The app had no splash/launch theme and ran on Theme.Material.Light, so cold
start flashed a white window and the Android 12+ system splash drew the icon on
white before the dark IDE. Theme.JCode now sets a dark windowBackground +
status/nav colors (@color/ic_launcher_background #151922) and, on API 31+, the
splash background + the JCode logo as windowSplashScreenAnimatedIcon; the
manifest applies it. Device-verified: cold start shows a dark splash with the
centered logo, cross-fading straight into the dark editor.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Output panel: real build/run log (teed from run terminals) + app activity
The right-drawer Output tab was a static placeholder; make it a real, read-only,
auto-following log. New OutputLog bus collects: run lifecycle headers (handleRun),
the app message stream (emitMessage), and the actual build output teed from the
run terminals — TerminalSessionManager gained an onOutput(id, buffer, len) hook
that TerminalSessionHost routes into OutputLog, which assembles lines and strips
ANSI/VT escapes. OutputSidebarContent renders it (monospace, colored by kind,
auto-follow only when pinned to bottom, Clear button).
Two bugs fixed during device verification: OutputLog.captured must be @Volatile
(the PTY reader thread read a stale empty set and dropped all teed output); and
cleanLine must drop the trailing CR before its \r-overwrite collapse, else CRLF
terminal lines blank out entirely.
Device-verified: Run an ASP.NET+Vite project → Output shows the run header, the
dotnet build log, and the npm/Vite log, ANSI-stripped and correctly lined.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Managers: shared header (Search/Refresh + "N installed") + flat installed-first lists
Redesign the Extensions, SDK, and LSP manager panels to a consistent layout. New
shared ManagerPanelHeader (core/design): title + icon-only Search and Refresh
buttons, an "N installed" count, and a toggle-to-filter search field. Each panel
now renders ONE flat list sorted installed/updatable-first then by name, filtered
by the search query — replacing the old per-category section cards (SDK/LSP) and
the split Marketplace/Installed sections (Extensions). For SDK/LSP the category is
shown as each row's subtitle so grouping info isn't lost.
Device-verified: Extensions shows "4 installed" + installed-first list + live
search filter; SDK shows "2 installed" with Node/Python on top + category
subtitles; LSP shows "0 installed" + category subtitles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Extensions: drop the built-in Theme/Icon bundle sections (they live in Settings)
Theme and icon bundles are built-in app customizations, not marketplace
extensions, and are already fully managed in Settings → Appearance. Showing them
in the Extensions list was redundant and out of place, so remove the two sections
(and the now-dead BundleGalleryRow + the themeBundleId/onSelectTheme/iconBundleId/
onSelectIcon params + their call-site args + unused imports). The Extensions panel
is now purely the installed/marketplace extension list. If theme/icon packs ever
become installable extensions (the .jext spec reserves type: theme / type: icons),
they'll appear in the list naturally as installed packs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>1 parent 0bd6ec7 commit 67a96c3
329 files changed
Lines changed: 36949 additions & 0 deletions
File tree
- .github/workflows
- app
- src
- androidTest/java/dev/jcode
- main
- java/dev/jcode
- backend
- editor
- run
- workbench
- dialog
- marketplace
- res
- mipmap-anydpi-v26
- mipmap-hdpi
- mipmap-mdpi
- mipmap-xhdpi
- mipmap-xxhdpi
- mipmap-xxxhdpi
- values
- xml
- core
- adaptive
- src/main
- java/dev/jcode
- adaptive
- core/adaptive
- buffer
- src/main
- java/dev/jcode/core/buffer
- config
- src/main
- java/dev/jcode/core/config
- schema
- ctags
- src/main
- java/dev/jcode
- core/ctags
- ctags
- debug
- src/main
- java/dev/jcode
- core/debug
- debug
- design
- src/main
- java/dev/jcode
- core/design
- design
- distro
- src/main
- assets/distro
- java/dev/jcode
- core/distro
- distro
- editor-completion
- src/main
- java/dev/jcode/core/editor/completion
- editor-decor
- src/main
- java/dev/jcode/core/editor/decor
- editor
- src/main
- java/dev/jcode/core/editor
- decor
- ext
- src/main
- java/dev/jcode
- core/ext
- ext
- fs
- src/main
- java/dev/jcode
- core/fs
- fs
- lsp
- src/main
- java/dev/jcode/core/lsp
- resource
- src/main
- java/dev/jcode/core/resource
- search
- src/main
- java/dev/jcode/core/search
- state
- src/main
- java/dev/jcode
- core/state
- state
- term
- src/main
- java/dev/jcode
- core/term
- term
- treesitter
- src/main
- java/dev/jcode/core/treesitter
- vcs
- src/main
- java/dev/jcode
- core/vcs
- vcs
- feature
- debug
- src/main
- java/dev/jcode/feature/debug
- editor-pane
- src/main
- java/dev/jcode/feature/editor/pane
- explorer
- src/main
- java/dev/jcode/feature/explorer
- lsp-manager
- src/main/java/dev/jcode/feature/lspmanager
- marketplace
- src/main
- java/dev/jcode/feature/marketplace
- onboarding
- src/main
- java/dev/jcode/feature/onboarding
- problems
- src/main
- java/dev/jcode/feature/problems
- scm
- src/main
- java/dev/jcode/feature/scm
- sdk-manager
- src/main
- java/dev/jcode/feature/sdkmanager
- search
- src/main
- java/dev/jcode/feature
- featuresearch
- search
- settings
- src/main
- java/dev/jcode/feature/settings
- terminal-pane
- src/main
- java/dev/jcode/feature
- terminalpane
- terminal/pane
- gradle
- wrapper
- native
- buffer
- src
- main
- java/dev/jcode/native/buffer
- common
- core
- src/main
- cpp
- java/dev/jcode/native/core
- editor-render
- src/main
- java/dev/jcode/native/editorrender
- grammars
- src/main
- libgit2
- src/main
- java/dev/jcode/native/libgit2
- proot
- src/main
- assets/bin
- java/dev/jcode/native/proot
- pty
- src
- main
- java/dev/jcode/native/pty
- ripgrep-ffi
- rust
- src
- src/main
- java/dev/jcode/native/ripgrepffi
- tree-sitter
- src
- main
- java/dev/jcode/native/treesitter
- vt
- src
- main
- java/dev/jcode/native/vt
- wasmtime-ffi
- rust
- src
- src/main
- java/dev/jcode/native/wasmtimeffi
- plans
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
Loading
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
Lines changed: 29 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
0 commit comments