Archive Steam docs; add itch.io upload guide
Move legacy Steam-related docs into documentation/archived and add a new UPLOAD-ITCH.md describing how to publish builds to itch.io with Butler. Update top-level README to state official releases will be on itch.io. Remove Steam-specific UI/features: drop steamCloudOptIn from first-run preferences, remove the Steam Cloud teaser and summary from the setup flow, and adjust settings/setup copy to reference handheld devices and non‑Steam distribution. Also make a small wording tweak in the plugins doc about rendererPreload.
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
# Uploading releases to itch.io using Butler
|
||||
|
||||
This document explains how to prepare and upload Nebula releases to itch.io using Butler, covering macOS, Windows, and Linux.
|
||||
|
||||
## Overview
|
||||
|
||||
Butler (itch.io) is the recommended channel for distributing Nebula releases. The process is:
|
||||
|
||||
1. Build your platform-specific artifact.
|
||||
2. Install and authenticate `butler`.
|
||||
3. Push the build to your `username/game:channel` with `butler push`.
|
||||
|
||||
## Install Butler
|
||||
|
||||
- macOS / Linux / Windows: Download the appropriate Butler binary from the official itch.io Butler releases (follow the official docs). Unpack, make executable, and place it on your `PATH`.
|
||||
|
||||
Example (Linux/macOS):
|
||||
|
||||
```bash
|
||||
# after downloading 'butler' binary
|
||||
chmod +x butler
|
||||
sudo mv butler /usr/local/bin/
|
||||
```
|
||||
|
||||
On Windows, place `butler.exe` in a folder on `PATH` or use it directly from the build folder.
|
||||
|
||||
## Authenticate
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
butler login
|
||||
```
|
||||
|
||||
This opens a browser-based authentication flow. Verify with:
|
||||
|
||||
```bash
|
||||
butler whoami
|
||||
```
|
||||
|
||||
If automation is required, consult the official Butler docs for API-key/token login options.
|
||||
|
||||
## Prepare platform artifacts
|
||||
|
||||
macOS
|
||||
- Create a zip of your `.app` bundle (keep the `.app` as a top-level item inside the zip):
|
||||
|
||||
```bash
|
||||
ditto -c -k --sequesterRsrc --keepParent MyApp.app MyApp-mac.zip
|
||||
```
|
||||
|
||||
Windows
|
||||
- Zip the folder containing your `.exe` and runtime files, or create an installer and zip the installer.
|
||||
|
||||
```powershell
|
||||
Compress-Archive -Path .\build\MyApp\* -DestinationPath MyApp-windows.zip
|
||||
```
|
||||
|
||||
Linux
|
||||
- Create a tarball (or zip) of the Linux runtime files:
|
||||
|
||||
```bash
|
||||
tar -czf MyApp-linux.tar.gz -C build/linux .
|
||||
```
|
||||
|
||||
Note: ensure the main binary has executable permissions before archiving.
|
||||
|
||||
## Push to itch.io
|
||||
|
||||
Basic command:
|
||||
|
||||
```bash
|
||||
butler push <path> <username>/<game>:<channel>
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# macOS build
|
||||
butler push MyApp-mac.zip myuser/nebulabrowser:mac
|
||||
|
||||
# Windows build
|
||||
butler push MyApp-windows.zip myuser/nebulabrowser:windows
|
||||
|
||||
# Linux build
|
||||
butler push MyApp-linux.tar.gz myuser/nebulabrowser:linux
|
||||
```
|
||||
|
||||
Set a release version for itch.io using `--userversion`:
|
||||
|
||||
```bash
|
||||
butler push MyApp-mac.zip myuser/nebulabrowser:mac --userversion 1.2.3
|
||||
```
|
||||
|
||||
## Recommended channel strategy
|
||||
|
||||
- `stable` or `default` — production releases
|
||||
- `beta` — pre-release testing
|
||||
- Use platform-specific channels (e.g., `mac`, `windows`, `linux`) if you want separate channels per OS
|
||||
|
||||
## Tips
|
||||
|
||||
- Keep artifacts small and platform-specific to reduce download size.
|
||||
- Verify the upload with `butler whoami` and by visiting your game page on itch.io.
|
||||
- When testing on macOS, notarization and Gatekeeper may affect distribution; provide clear install instructions on your itch page.
|
||||
|
||||
## Rollback
|
||||
|
||||
Butler supports pushing to a channel multiple times; the latest pushed build becomes the current for that channel. To revert, push a previous artifact or use the itch.io web UI to select a previous build.
|
||||
|
||||
## References
|
||||
|
||||
Consult the official Butler documentation for advanced usage (credentials, automated CI uploads, delta uploads, and platform-specific packaging recommendations).
|
||||
@@ -0,0 +1,94 @@
|
||||
# Electron Upgrade Feature - Bug Fixes
|
||||
|
||||
## Problem Identified
|
||||
The upgrade feature was downloading and installing new Electron versions successfully, but the app always showed the old version (1.0.0) after restart because:
|
||||
|
||||
1. **Version Source Issue**: The app was reading `app.getVersion()` which gets the version from `package.json` at startup time
|
||||
2. **Package.json Not Re-read**: Even after npm installed a new Electron version, the app didn't re-read the updated `package.json`
|
||||
3. **Runtime Display**: The About tab showed the bundled Electron version (37.x) which is baked into the binary at build time
|
||||
|
||||
## Solutions Implemented
|
||||
|
||||
### 1. **New Helper Function: `getInstalledElectronVersion()`**
|
||||
- Reads `package.json` directly every time it's called (not cached)
|
||||
- Extracts the actual installed Electron version from `devDependencies`
|
||||
- Handles both stable (`electron`) and nightly (`electron-nightly`) packages
|
||||
- Strips version specifiers (^, ~, etc.) to get the clean version number
|
||||
- Falls back to `app.getVersion()` if reading fails
|
||||
|
||||
```javascript
|
||||
function getInstalledElectronVersion() {
|
||||
try {
|
||||
const packageJsonPath = path.join(__dirname, 'package.json');
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||
|
||||
const electronDep = packageJson.devDependencies?.electron;
|
||||
const electronNightlyDep = packageJson.devDependencies?.['electron-nightly'];
|
||||
|
||||
if (electronDep) {
|
||||
return electronDep.replace(/^\D+/, '');
|
||||
}
|
||||
if (electronNightlyDep) {
|
||||
return electronNightlyDep.replace(/^\D+/, '');
|
||||
}
|
||||
return app.getVersion();
|
||||
} catch (err) {
|
||||
return app.getVersion();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. **Updated `get-electron-versions` Handler**
|
||||
- Now uses `getInstalledElectronVersion()` instead of `app.getVersion()`
|
||||
- Returns the actual installed version that was modified by npm
|
||||
- Performs fresh version checks each time (no caching)
|
||||
|
||||
### 3. **Improved `upgrade-electron` Handler**
|
||||
- Increased `maxBuffer` to handle large npm output
|
||||
- Added cleanup logic to remove the old Electron variant when switching types
|
||||
- Removes `electron` when upgrading to `nightly`
|
||||
- Removes `electron-nightly` when upgrading to `stable`
|
||||
- Better error logging to debug npm failures
|
||||
- Returns clearer messages about installation status
|
||||
|
||||
### 4. **Enhanced UI/UX in settings.js**
|
||||
- Added more descriptive status text ("Downloading and installing..." instead of just "Upgrading...")
|
||||
- Disables all controls during upgrade to prevent multiple clicks
|
||||
- Reduced restart delay from 2000ms to 1500ms for faster feedback
|
||||
- Better error handling with proper cleanup of disabled states
|
||||
|
||||
## How It Works Now
|
||||
|
||||
1. **User clicks "Check for Updates"**
|
||||
- Queries npm registry for latest version
|
||||
- Uses `getInstalledElectronVersion()` to read current version from `package.json`
|
||||
- Compares versions and shows if update is available
|
||||
|
||||
2. **User clicks "Upgrade Electron"**
|
||||
- Confirms action
|
||||
- Runs `npm install --save-dev electron@latest` (or `electron-nightly@latest`)
|
||||
- npm downloads and installs new version
|
||||
- Handler removes the other Electron variant from `package.json` if needed
|
||||
- Shows success message
|
||||
|
||||
3. **App Restarts**
|
||||
- Uses `app.relaunch()` and `app.quit()`
|
||||
- When app relaunches, it:
|
||||
- Loads new Electron binary from `node_modules`
|
||||
- Runs new Electron version
|
||||
- Settings page shows correct new version on next check
|
||||
|
||||
## Testing Recommendations
|
||||
|
||||
1. Test upgrading from stable to nightly version
|
||||
2. Test upgrading from nightly back to stable
|
||||
3. Verify version display updates after restart
|
||||
4. Check that old variant is removed from `package.json`
|
||||
5. Verify app runs stably with new Electron version
|
||||
|
||||
## Notes for Future Development
|
||||
|
||||
- The About tab displays `process.versions.electron` which is the bundled Chromium version, not the Electron framework version
|
||||
- The Electron version we display in the upgrade section comes from `package.json` which is the actual framework version
|
||||
- When building with electron-builder, the bundled version becomes fixed until next rebuild
|
||||
- For development/testing, the upgrade feature reads live from `package.json`
|
||||
@@ -0,0 +1,3 @@
|
||||
# GPU-FIX-README (archived)
|
||||
|
||||
This file was present at repository root and has been archived here for reference. The canonical GPU fix documentation lives under `documentation/`.
|
||||
@@ -0,0 +1,98 @@
|
||||
# Nebula Plugins (Early Preview)
|
||||
|
||||
This document explains how to build simple plugins for Nebula. The initial API is intentionally small and will grow with feedback.
|
||||
|
||||
## Overview
|
||||
|
||||
- Plugins live under either of these folders:
|
||||
- App folder: `<app>/plugins/<plugin-id>/`
|
||||
- User folder: `%APPDATA%/Nebula/plugins/<plugin-id>/` (Windows) – preferred for user-installed plugins.
|
||||
- Each plugin has a `plugin.json` manifest. Optional `main.js` runs in the main process. Optional `renderer-preload.js` runs in the renderer preload context and can expose safe APIs via `contextBridge`.
|
||||
- Plugins are loaded on app start. Toggle a plugin by setting `"enabled": false` in its manifest.
|
||||
|
||||
## Manifest (plugin.json)
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "my-plugin",
|
||||
"name": "My Plugin",
|
||||
"version": "0.1.0",
|
||||
"description": "What it does",
|
||||
"main": "main.js",
|
||||
"rendererPreload": "renderer-preload.js",
|
||||
"categories": ["Search", "Productivity"],
|
||||
"authors": ["Jane Doe", { "name": "Acme Labs", "email": "oss@acme.example" }],
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
Fields:
|
||||
- id: Unique id. Defaults to folder name if omitted.
|
||||
- main: Optional entry for main process integration.
|
||||
- rendererPreload: Optional file injected into the preload. Use it to expose a safe surface to the page.
|
||||
- categories: Optional string or array of strings used for organizing/filtering plugins in UI and APIs. Example: ["AI", "Utilities"].
|
||||
- authors: Optional string or array of strings/objects describing authors. Objects support { name, email, url }. In APIs/UI, names are displayed.
|
||||
- enabled: Defaults to true.
|
||||
|
||||
## Main process API (activate)
|
||||
|
||||
If `main` is present, export an `activate(ctx)` function. The `ctx` contains:
|
||||
- Electron: `app`, `BrowserWindow`, `ipcMain`, `session`, `Menu`, `dialog`, `shell`
|
||||
- paths: `{ appPath, userData, pluginDir }`
|
||||
- log/warn/error: prefix logs with your plugin id
|
||||
- on(event, cb): subscribe to lifecycle events (experimental)
|
||||
- registerIPC(channel, handler): quickly expose an `ipcMain.handle`
|
||||
- registerWebRequest(filter, listener): attach `session.webRequest.onBeforeRequest`
|
||||
|
||||
Example:
|
||||
|
||||
module.exports.activate = (ctx) => {
|
||||
ctx.log('hello');
|
||||
ctx.registerIPC('my-plugin:do', async (_evt, payload) => ({ ok: true }));
|
||||
ctx.registerWebRequest({ urls: ['*://*/*'] }, (details) => ({ cancel: false }));
|
||||
};
|
||||
|
||||
## Renderer preload API
|
||||
|
||||
If `rendererPreload` is present, it will be `require()`-d from the app preload. You can use `contextBridge` to expose a safe surface to the page:
|
||||
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
contextBridge.exposeInMainWorld('myPlugin', {
|
||||
hello: () => ipcRenderer.invoke('my-plugin:do'),
|
||||
});
|
||||
|
||||
Your exposed API will be available on `window.myPlugin` in `renderer/` code (e.g., `script.js`).
|
||||
|
||||
## Sample plugin
|
||||
|
||||
A working sample is included at `plugins/sample-hello/`:
|
||||
- Adds menu item "Say Hello (Sample Plugin)" under Help.
|
||||
- Exposes `window.sampleHello.ping()` and `window.sampleHello.onHello(cb)`.
|
||||
|
||||
Try it from the DevTools console:
|
||||
|
||||
await window.sampleHello.ping();
|
||||
window.sampleHello.onHello((m) => console.log('got hello', m));
|
||||
|
||||
Click Help -> Say Hello (Sample Plugin) to see the message delivered to the page.
|
||||
|
||||
## Loading order and safety
|
||||
|
||||
- Plugins load after the app is ready. Renderer preloads run after Nebula's own preload has exposed its APIs.
|
||||
- Context isolation stays enabled. Only data explicitly exposed via `contextBridge` is available to pages.
|
||||
- Avoid long blocking work in plugin activation.
|
||||
|
||||
## Debugging
|
||||
|
||||
- See logs with a `[Plugin:<id>]` prefix in the app console.
|
||||
- Temporarily disable a plugin by setting `enabled: false` in `plugin.json`.
|
||||
|
||||
## Roadmap
|
||||
|
||||
This is a first pass. Planned next:
|
||||
- Enable plugin settings UI
|
||||
- Hot reload/reload button
|
||||
- More lifecycle hooks (tab events, context menu contributions)
|
||||
- Theming hooks
|
||||
@@ -0,0 +1,238 @@
|
||||
Converting extracted AppImage (`squashfs-root`) into a distributable AppDir for Steam
|
||||
|
||||
> Note: Nebula will not be distributed on the Steam Store. This document is
|
||||
> kept for reference and for users who run Nebula via Steam as a non-Steam
|
||||
> shortcut. Official distribution will be handled via itch.io and other
|
||||
> non-Steam channels.
|
||||
|
||||
If your environment lacks `rsync`, use `cp -a` to copy the extracted AppImage into a clean AppDir and prepare it for upload to Steam.
|
||||
|
||||
1) Copy the extracted AppImage to an AppDir folder
|
||||
```bash
|
||||
cp -a squashfs-root/ nebula-appdir
|
||||
```
|
||||
|
||||
2) Unpack `app.asar` to edit or include app sources (optional; requires `npx asar`)
|
||||
```bash
|
||||
cd nebula-appdir/resources
|
||||
npx asar extract app.asar app
|
||||
# keep a backup if you want
|
||||
mv app app.orig && rm app.asar
|
||||
cd ../../
|
||||
```
|
||||
|
||||
3) Add/verify launcher (we added `nebula-appdir/Nebula`):
|
||||
```bash
|
||||
chmod +x nebula-appdir/Nebula
|
||||
```
|
||||
Run locally:
|
||||
```bash
|
||||
cd nebula-appdir
|
||||
./Nebula
|
||||
```
|
||||
|
||||
4) Ensure binary & permissions are correct
|
||||
```bash
|
||||
chmod +x nebula-appdir/nebula
|
||||
```
|
||||
|
||||
5) Package or upload to Steam
|
||||
- Create a tarball to upload as game files, or upload the AppDir contents as the depot.
|
||||
```bash
|
||||
tar -czf nebula-appdir.tar.gz -C nebula-appdir .
|
||||
```
|
||||
- In Steamworks, set the launch command to `./Nebula` (or `./nebula`).
|
||||
|
||||
Notes
|
||||
- `--no-sandbox` reduces Chromium sandboxing; prefer fixing `chrome-sandbox` and enabling sandboxing when possible.
|
||||
- Using the AppDir avoids AppImage/FUSE dependency on target systems.
|
||||
- Test on a clean SteamOS/Deck image before publishing.
|
||||
|
||||
Big Picture auto-start (SteamOS Gaming Mode)
|
||||
- If Nebula is launched from SteamOS Gaming Mode, it will auto-start in Big Picture Mode.
|
||||
- To force/disable via Steam Launch Options: `--big-picture` or `--no-big-picture`.
|
||||
|
||||
---
|
||||
|
||||
## Built-in Controller Support (Steam Deck / Game Mode)
|
||||
|
||||
Nebula has **native gamepad support** that signals to Steam that the application is consuming controller input. This prevents Steam from applying Desktop mouse/keyboard emulation when running in Game Mode.
|
||||
|
||||
### How It Works
|
||||
|
||||
Steam Deck only stops applying Desktop mouse emulation when:
|
||||
1. The application actively reads controller/gamepad input, OR
|
||||
2. Steam Input is enabled (which requires explicit configuration)
|
||||
|
||||
If an app does not read controller input at all, Steam assumes the user needs mouse emulation.
|
||||
|
||||
Nebula solves this by:
|
||||
1. **Preload Gamepad Handler**: The preload script (`preload.js`) continuously polls `navigator.getGamepads()` from the moment any window loads. This signals to Steam that the app is consuming gamepad events and should not apply mouse emulation.
|
||||
2. **Big Picture Mode**: Full controller-friendly UI with:
|
||||
- D-pad / Left stick: Navigate menus
|
||||
- A button: Select/activate
|
||||
- B button: Back
|
||||
- X button: Backspace (in keyboard)
|
||||
- Y button: Space / Open search
|
||||
- LB/RB: Navigate webview history
|
||||
- Right stick: Virtual cursor (in browse mode)
|
||||
- Triggers: Left/right click (in browse mode)
|
||||
- Start: Toggle settings/sidebar
|
||||
- Select: Toggle fullscreen webview
|
||||
|
||||
### Gamepad API (for Developers)
|
||||
|
||||
The gamepad handler exposes an API via `window.gamepadAPI`.
|
||||
|
||||
```javascript
|
||||
// Check if gamepad handler is initialized
|
||||
if (gamepadAPI.isAvailable()) {
|
||||
console.log('Gamepad handler is running');
|
||||
}
|
||||
|
||||
// Check if a gamepad is connected
|
||||
if (gamepadAPI.isConnected()) {
|
||||
console.log('Gamepad connected!');
|
||||
}
|
||||
|
||||
// Get list of connected gamepads
|
||||
const gamepads = gamepadAPI.getConnected();
|
||||
// Returns: [{ id, index, mapping, buttons, axes }, ...]
|
||||
console.log(gamepads);
|
||||
|
||||
// Get active gamepad's current state (buttons and axes)
|
||||
const active = gamepadAPI.getActive();
|
||||
if (active) {
|
||||
console.log('Active gamepad:', active.id);
|
||||
console.log('Buttons:', active.buttons);
|
||||
console.log('Axes:', active.axes);
|
||||
}
|
||||
|
||||
// Get handler state for debugging
|
||||
const state = gamepadAPI.getState();
|
||||
console.log('Handler state:', state);
|
||||
// Returns: { initialized, connectedCount, activeGamepadIndex, isPolling }
|
||||
|
||||
// Listen for gamepad events (via CustomEvent on window)
|
||||
window.addEventListener('nebula-gamepad-button', (e) => {
|
||||
const { button, pressed, value } = e.detail;
|
||||
console.log(`Button ${button}: ${pressed ? 'pressed' : 'released'}`);
|
||||
});
|
||||
|
||||
window.addEventListener('nebula-gamepad-connect', (e) => {
|
||||
console.log('Gamepad connected:', e.detail.id);
|
||||
});
|
||||
|
||||
window.addEventListener('nebula-gamepad-disconnect', (e) => {
|
||||
console.log('Gamepad disconnected:', e.detail.id);
|
||||
});
|
||||
|
||||
window.addEventListener('nebula-gamepad-axis', (e) => {
|
||||
const { axis, value } = e.detail;
|
||||
console.log(`Axis ${axis}: ${value}`);
|
||||
});
|
||||
|
||||
// Enable debug logging
|
||||
gamepadAPI.setDebug(true);
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If Steam is still applying mouse emulation:
|
||||
|
||||
1. **Configure Steam Input per-game** (most reliable fix):
|
||||
- **Windows / Desktop Steam UI**:
|
||||
- Library → right-click Nebula → Properties → **Controller**
|
||||
- Set **"Override for Nebula"** to **"Disable Steam Input"**
|
||||
- **Steam Deck / SteamOS Gaming Mode**:
|
||||
- Open Nebula → press the Steam button → **Controller Settings** (or the controller icon)
|
||||
- Set the layout to a **Gamepad** template (not “Keyboard/Mouse”), or disable Steam Input if the toggle is available
|
||||
|
||||
If you **don’t see a Controller tab** (common when the Steam entry is treated as an “application/tool”):
|
||||
- Use **Big Picture / Gaming Mode** and edit the **Controller Layout** for that specific entry.
|
||||
- Or change Steam’s global Desktop Layout: Steam → Settings → Controller → **Desktop Layout** → pick a gamepad-focused template or remove mouse/keyboard bindings.
|
||||
|
||||
2. **Verify gamepad polling is active**: Open DevTools (F12) and run `gamepadAPI.getState()` - check that `isPolling` is `true`
|
||||
3. **Check gamepad connection**: Run `gamepadAPI.getConnected()` to see detected gamepads
|
||||
4. **Press a button first**: On Linux, the `gamepadconnected` event may not fire until the first button press
|
||||
5. **Enable debug mode**: Run `gamepadAPI.setDebug(true)` to see detailed logs
|
||||
6. **Restart the app**: Close Nebula completely and relaunch from Steam
|
||||
|
||||
### Steam Launch Options
|
||||
|
||||
#### Windows
|
||||
|
||||
The `VAR=value %command%` syntax does **not** work on Windows. Use the Steam UI instead:
|
||||
|
||||
1. **Library** → right-click Nebula → **Properties** → **Controller** → set to **"Disable Steam Input"**
|
||||
2. If no Controller tab exists, open Steam in **Big Picture Mode** → Nebula → **Manage Game** (gear) → **Controller Options** → **Disable Steam Input**
|
||||
|
||||
If you must use launch options on Windows, use this wrapper syntax:
|
||||
```bat
|
||||
cmd /c "set SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD=0 && %command%"
|
||||
```
|
||||
|
||||
#### Linux / SteamOS / Steam Deck
|
||||
|
||||
Add these to your Steam launch options (Right-click game → Properties → Launch Options):
|
||||
|
||||
```bash
|
||||
# Disable Steam Input completely (recommended for native controller support)
|
||||
SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD=0 %command%
|
||||
|
||||
# Force native gamepad without Steam's emulation layer
|
||||
STEAM_INPUT_ENABLE_VIRTUAL_GAMEPAD=0 %command%
|
||||
|
||||
# Combined - full native controller mode with Big Picture UI
|
||||
SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD=0 STEAM_INPUT_ENABLE_VIRTUAL_GAMEPAD=0 %command% --big-picture
|
||||
|
||||
# If you need to debug controller issues
|
||||
SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD=0 %command% --big-picture 2>&1 | tee ~/nebula-debug.log
|
||||
```
|
||||
|
||||
### Steam Deck Recommended Setup
|
||||
|
||||
For the best experience on Steam Deck:
|
||||
|
||||
1. **Add Nebula as a Non-Steam Game** (if not using Steamworks version)
|
||||
2. **Controller Settings**:
|
||||
- Right-click Nebula → Properties → Controller
|
||||
- Set to **"Disable Steam Input"**
|
||||
3. **Launch Options**:
|
||||
```
|
||||
SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD=0 STEAM_INPUT_ENABLE_VIRTUAL_GAMEPAD=0 %command% --big-picture
|
||||
```
|
||||
4. **Shortcuts** (optional):
|
||||
- Configure gamepad shortcuts in Steam for Steam button actions (screenshots, etc.)
|
||||
|
||||
### Why This Is Needed
|
||||
|
||||
Steam Deck / SteamOS Game Mode applies "Desktop Configuration" mouse/keyboard emulation to apps that don't appear to handle controller input. Even though Nebula polls `navigator.getGamepads()` continuously, Steam's input layer initializes before the app can signal its intent.
|
||||
|
||||
The solution is two-fold:
|
||||
1. **Environment variables** (`SDL_GAMECONTROLLER_*`) signal to Steam's SDL-based input layer early
|
||||
2. **Steam Input settings** ("Disable Steam Input") bypasses the emulation entirely
|
||||
|
||||
### Shipping Defaults (Steamworks “Software/App” limitation)
|
||||
|
||||
If your Steamworks package is categorized as **Software/Application**, Steamworks may not expose per-title Steam Input configuration the way it does for games.
|
||||
|
||||
In that case:
|
||||
- You generally **cannot force a global Steam Input toggle** for all users from Steamworks.
|
||||
- The practical, shippable approach is to (a) **consume controller input natively** (Nebula does this via early Gamepad API polling) so Steam Deck/Game Mode backs off Desktop emulation, and (b) provide user-facing guidance for disabling Steam Input / choosing a Gamepad layout.
|
||||
|
||||
If you need Steam Input defaults controlled centrally, the usual path is to ask Valve Partner Support to enable the relevant Steam Input configuration for your App ID, or to re-categorize the title where appropriate.
|
||||
|
||||
### Force Big Picture Mode
|
||||
|
||||
```bash
|
||||
# Via command line
|
||||
./Nebula --big-picture
|
||||
|
||||
# Via environment
|
||||
NEBULA_BIG_PICTURE=1 ./Nebula
|
||||
|
||||
# Disable Big Picture Mode
|
||||
./Nebula --no-big-picture
|
||||
NEBULA_NO_BIG_PICTURE=1 ./Nebula
|
||||
```
|
||||
@@ -0,0 +1,212 @@
|
||||
# Linux / SteamOS Build Upload Guide (SteamCMD)
|
||||
|
||||
> Note: Nebula will not be distributed on the Steam Store. This Steam upload
|
||||
> guide is retained for historical/reference purposes only. Official releases
|
||||
> will be published on itch.io and other non-Steam channels.
|
||||
|
||||
This guide explains how to upload the **Linux / SteamOS** build of Nebula Browser to Steam using **SteamCMD**. It is tailored to the current project layout on Steam Deck / Linux.
|
||||
|
||||
---
|
||||
|
||||
## App & Depot IDs
|
||||
|
||||
* **App ID:** 4290110
|
||||
* **Windows Depot:** 4290111
|
||||
* **Linux / SteamOS Depot:** 4290112
|
||||
|
||||
This guide covers **Linux only (4290112)**.
|
||||
|
||||
---
|
||||
|
||||
## Current Folder Layout
|
||||
|
||||
Home directory layout expected by this guide:
|
||||
|
||||
```
|
||||
/home/deck/
|
||||
├── steamcmd/
|
||||
│ └── steamcmd.sh
|
||||
└── steam_build/
|
||||
├── Nebula_SteamOS/
|
||||
│ ├── nebula-browser
|
||||
│ └── ...other runtime files...
|
||||
├── output/
|
||||
└── app_4290110.vdf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Verify Executable Permissions
|
||||
|
||||
The main Linux binary **must** be executable.
|
||||
|
||||
```bash
|
||||
cd ~/steam_build/Nebula_SteamOS
|
||||
ls -la
|
||||
```
|
||||
|
||||
Identify the main binary (for example `nebula-browser`) and run:
|
||||
|
||||
```bash
|
||||
chmod +x nebula-browser
|
||||
```
|
||||
|
||||
Optional quick test:
|
||||
|
||||
```bash
|
||||
./nebula-browser
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: App Build VDF Configuration
|
||||
|
||||
Ensure `app_4290110.vdf` exists at:
|
||||
|
||||
```
|
||||
/home/deck/steam_build/app_4290110.vdf
|
||||
```
|
||||
|
||||
Its contents should be **exactly**:
|
||||
|
||||
```
|
||||
"AppBuild"
|
||||
{
|
||||
"AppID" "4290110"
|
||||
"Desc" "Nebula SteamOS build"
|
||||
"BuildOutput" "output/"
|
||||
"ContentRoot" "."
|
||||
"Preview" "0"
|
||||
|
||||
"Depots"
|
||||
{
|
||||
"4290112"
|
||||
{
|
||||
"FileMapping"
|
||||
{
|
||||
"LocalPath" "Nebula_SteamOS/*"
|
||||
"DepotPath" "."
|
||||
"recursive" "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
* `ContentRoot "."` maps to `/home/deck/steam_build`
|
||||
* `Nebula_SteamOS/*` uploads everything inside that folder
|
||||
* Depot **4290112** is the Linux / SteamOS depot
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Launch SteamCMD
|
||||
|
||||
```bash
|
||||
cd ~/steamcmd
|
||||
./steamcmd.sh
|
||||
```
|
||||
|
||||
You should now see:
|
||||
|
||||
```
|
||||
Steam>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Log In
|
||||
|
||||
Inside SteamCMD:
|
||||
|
||||
```
|
||||
login YOUR_STEAM_USERNAME
|
||||
```
|
||||
|
||||
If Steam Guard is enabled, enter the code when prompted.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Upload the Linux Build
|
||||
|
||||
Inside SteamCMD, run **with full path** (no `~`):
|
||||
|
||||
```
|
||||
run_app_build /home/deck/steam_build/app_4290110.vdf
|
||||
```
|
||||
|
||||
Expected behavior:
|
||||
|
||||
* Content scan starts
|
||||
* Files upload to depot 4290112
|
||||
* A **BuildID** is printed on success
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Exit SteamCMD
|
||||
|
||||
```
|
||||
quit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 7: Assign Build to a Branch (Required)
|
||||
|
||||
After upload completes:
|
||||
|
||||
1. Open **Steamworks**
|
||||
2. Go to **Nebula Browser → Builds**
|
||||
3. Find the new Build ID
|
||||
4. Assign it to a branch (`internal`, `beta`, or `default`)
|
||||
|
||||
If this step is skipped, Steam Deck installs **0 bytes** even though upload succeeded.
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
### App build file does not exist
|
||||
|
||||
* SteamCMD does **not** expand `~`
|
||||
* Always use `/home/deck/...` absolute paths
|
||||
|
||||
### 0 bytes uploaded
|
||||
|
||||
* `LocalPath` is wrong
|
||||
* Build not assigned to a branch
|
||||
* Executable missing or filtered out
|
||||
|
||||
### Build installs but does not launch
|
||||
|
||||
* Binary not executable
|
||||
* Missing runtime libraries
|
||||
* Incorrect launch configuration in Steamworks
|
||||
|
||||
---
|
||||
|
||||
## Logs
|
||||
|
||||
If something fails, check:
|
||||
|
||||
```bash
|
||||
tail -n 200 /home/deck/.local/share/Steam/logs/stderr.txt
|
||||
tail -n 200 /home/deck/.local/share/Steam/logs/bootstrap_log.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes for Steam Deck
|
||||
|
||||
* Test in **Desktop Mode** first
|
||||
* Then test in **Gaming Mode**
|
||||
* Steam Input and sandboxing differ between modes
|
||||
* Avoid absolute paths inside the app
|
||||
|
||||
---
|
||||
|
||||
## Status
|
||||
|
||||
This process uploads **Linux / SteamOS depot 4290112 only**.
|
||||
Windows builds should be uploaded separately using depot **4290111**.
|
||||
@@ -0,0 +1,133 @@
|
||||
# Nebot Typing Animation Feature
|
||||
|
||||
## Overview
|
||||
Added a realistic typing animation to the Nebot chat interface that makes AI responses appear character by character, similar to ChatGPT and other modern AI chat interfaces.
|
||||
|
||||
## Features Added
|
||||
|
||||
### 1. **Typing Animation**
|
||||
- Characters appear one by one instead of instantly
|
||||
- Smooth, natural typing rhythm
|
||||
- Configurable typing speed
|
||||
- Blinking cursor indicator during typing
|
||||
|
||||
### 2. **Settings Integration**
|
||||
- **Enable/Disable Toggle**: Users can turn typing animation on/off
|
||||
- **Speed Control**: Adjustable from 10-200 characters per second
|
||||
- **Live Preview**: Speed indicator updates in real-time
|
||||
- **Persistent Settings**: Preferences are saved and restored
|
||||
|
||||
### 3. **Smart Behavior**
|
||||
- **Queue Management**: Handles fast token streams efficiently
|
||||
- **Graceful Fallback**: Falls back to instant display if disabled
|
||||
- **Markdown Rendering**: Waits for typing to complete before rendering markdown
|
||||
- **Auto-scroll**: Maintains scroll position during animation
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
### Code Changes Made:
|
||||
|
||||
#### 1. **page.js** - Main Logic
|
||||
```javascript
|
||||
// Typing animation state
|
||||
let typingQueue = [];
|
||||
let isTyping = false;
|
||||
let typingSpeed = 25; // milliseconds per character
|
||||
let typingEnabled = true; // can be toggled in settings
|
||||
|
||||
function startTypingAnimation(element) {
|
||||
if (isTyping || typingQueue.length === 0) return;
|
||||
|
||||
isTyping = true;
|
||||
element.classList.add('typing');
|
||||
|
||||
function typeNext() {
|
||||
if (typingQueue.length === 0) {
|
||||
isTyping = false;
|
||||
element.classList.remove('typing');
|
||||
return;
|
||||
}
|
||||
|
||||
const char = typingQueue.shift();
|
||||
element.textContent += char;
|
||||
els.messages.scrollTop = els.messages.scrollHeight;
|
||||
|
||||
setTimeout(typeNext, typingSpeed);
|
||||
}
|
||||
|
||||
typeNext();
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. **page.css** - Visual Effects
|
||||
```css
|
||||
/* Typing animation cursor */
|
||||
.markdown.typing:after {
|
||||
content: "▋";
|
||||
color: var(--accent);
|
||||
animation: blink 1s infinite;
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 50% { opacity: 1; }
|
||||
51%, 100% { opacity: 0; }
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. **Settings UI** - User Controls
|
||||
- Checkbox to enable/disable typing animation
|
||||
- Range slider for speed control (10-200 chars/sec)
|
||||
- Real-time speed display
|
||||
- Proper styling for form elements
|
||||
|
||||
## User Experience
|
||||
|
||||
### Before:
|
||||
- Text appeared instantly when AI responded
|
||||
- No visual feedback during response generation
|
||||
- Less engaging interaction
|
||||
|
||||
### After:
|
||||
- Smooth character-by-character reveal
|
||||
- Blinking cursor shows active typing
|
||||
- Configurable speed for user preference
|
||||
- More engaging, human-like interaction
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
1. **Open Nebot**: Navigate to the Nebot page in Nebula Browser
|
||||
2. **Start a Chat**: Send a message to begin conversation
|
||||
3. **Watch the Animation**: AI responses will type out naturally
|
||||
4. **Customize Settings**:
|
||||
- Click the ⚙ Settings button
|
||||
- Toggle "Enable typing animation"
|
||||
- Adjust typing speed with the slider
|
||||
- Save changes
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- **Efficient Queuing**: Uses character queue to handle fast token streams
|
||||
- **Memory Friendly**: Minimal memory overhead
|
||||
- **Responsive**: Maintains smooth UI during animation
|
||||
- **Interruptible**: Can be disabled without restart
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential improvements could include:
|
||||
- Variable speed based on punctuation (pause at periods)
|
||||
- Sound effects for typing
|
||||
- Different animation styles
|
||||
- Per-conversation speed settings
|
||||
- Typing speed based on message length
|
||||
|
||||
## Testing
|
||||
|
||||
To test the feature:
|
||||
1. Start Nebula Browser (`npm start`)
|
||||
2. Navigate to Nebot page
|
||||
3. Send a message and observe the typing animation
|
||||
4. Try different speed settings in the settings panel
|
||||
5. Toggle the feature on/off to compare experiences
|
||||
|
||||
The typing animation enhances the user experience by making AI interactions feel more natural and engaging, similar to popular chat interfaces like ChatGPT.
|
||||
Reference in New Issue
Block a user