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:
2026-02-24 20:50:00 +13:00
parent 618ea7d12d
commit 0b51d133a4
13 changed files with 136 additions and 22 deletions
+113
View File
@@ -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`
+3
View File
@@ -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/`.
+98
View File
@@ -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
+238
View File
@@ -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 **dont 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 Steams 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.