Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 22 additions & 22 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ permissions:
contents: write

env:
TAURI_CONF: Ether/.html/src-tauri/tauri.conf.json
TAURI_CONF: @ether/.html/src-tauri/tauri.conf.json

jobs:
# ---------------------------------------------------------------------------
Expand All @@ -22,7 +22,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: Ether/.html/src-tauri/tauri.conf.json
sparse-checkout: @ether/.html/src-tauri/tauri.conf.json
sparse-checkout-cone-mode: false

- name: Extract version & check tag
Expand Down Expand Up @@ -64,21 +64,21 @@ jobs:
run: pip install pillow

- name: npm ci
working-directory: Ether/.html
working-directory: @ether/.html
run: npm ci

- name: Build Linux
working-directory: Ether/.html
working-directory: @ether/.html
run: npx tauri build --target x86_64-unknown-linux-gnu

- name: Upload Linux artifacts
uses: actions/upload-artifact@v4
with:
name: linux-artifacts
path: |
Ether/.html/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb
Ether/.html/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/*.rpm
Ether/.html/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage
@ether/.html/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/*.deb
@ether/.html/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/rpm/*.rpm
@ether/.html/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/*.AppImage

# ---------------------------------------------------------------------------
# Windows (cross-compile from Ubuntu)
Expand Down Expand Up @@ -109,19 +109,19 @@ jobs:
run: pip install pillow

- name: npm ci
working-directory: Ether/.html
working-directory: @ether/.html
run: npm ci

- name: Build Windows
working-directory: Ether/.html
working-directory: @ether/.html
run: npx tauri build --target x86_64-pc-windows-gnu

- name: Upload Windows artifacts
uses: actions/upload-artifact@v4
with:
name: windows-artifacts
path: |
Ether/.html/src-tauri/target/x86_64-pc-windows-gnu/release/bundle/nsis/*.exe
@ether/.html/src-tauri/target/x86_64-pc-windows-gnu/release/bundle/nsis/*.exe

# ---------------------------------------------------------------------------
# Android (APK + AAB)
Expand Down Expand Up @@ -160,11 +160,11 @@ jobs:
run: pip install pillow

- name: npm ci
working-directory: Ether/.html
working-directory: @ether/.html
run: npm ci

- name: Build Android
working-directory: Ether/.html
working-directory: @ether/.html
env:
NDK_HOME: ${{ env.ANDROID_HOME }}/ndk/27.0.12077973
run: |
Expand All @@ -177,8 +177,8 @@ jobs:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
run: |
APK_DIR="Ether/.html/src-tauri/gen/android/app/build/outputs/apk/universal/release"
AAB_DIR="Ether/.html/src-tauri/gen/android/app/build/outputs/bundle/universalRelease"
APK_DIR="@ether/.html/src-tauri/gen/android/app/build/outputs/apk/universal/release"
AAB_DIR="@ether/.html/src-tauri/gen/android/app/build/outputs/bundle/universalRelease"
UNSIGNED="$APK_DIR/app-universal-release-unsigned.apk"

# Rename APK
Expand All @@ -203,8 +203,8 @@ jobs:
with:
name: android-artifacts
path: |
Ether/.html/src-tauri/gen/android/app/build/outputs/apk/universal/release/Ether_*.apk
Ether/.html/src-tauri/gen/android/app/build/outputs/bundle/universalRelease/Ether_*.aab
@ether/.html/src-tauri/gen/android/app/build/outputs/apk/universal/release/Ether_*.apk
@ether/.html/src-tauri/gen/android/app/build/outputs/bundle/universalRelease/Ether_*.aab

# ---------------------------------------------------------------------------
# macOS (Apple Silicon) — uncomment when mac runner is available
Expand All @@ -226,19 +226,19 @@ jobs:
run: pip3 install --break-system-packages pillow

- name: npm ci
working-directory: Ether/.html
working-directory: @ether/.html
run: npm ci

- name: Build macOS (dmg)
working-directory: Ether/.html
working-directory: @ether/.html
run: npx tauri build --target aarch64-apple-darwin

- name: Upload macOS artifacts
uses: actions/upload-artifact@v4
with:
name: macos-artifacts
path: |
Ether/.html/src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/*.dmg
@ether/.html/src-tauri/target/aarch64-apple-darwin/release/bundle/dmg/*.dmg

# ---------------------------------------------------------------------------
# iOS — uncomment when mac runner is available
Expand Down Expand Up @@ -281,12 +281,12 @@ jobs:
#
# - name: npm ci
# if: steps.version.outputs.skip == 'false'
# working-directory: Ether/.html
# working-directory: @ether/.html
# run: npm ci
#
# - name: Build iOS (ipa)
# if: steps.version.outputs.skip == 'false'
# working-directory: Ether/.html
# working-directory: @ether/.html
# run: |
# npx tauri ios init
# npx tauri ios build --export-method debugging
Expand All @@ -297,7 +297,7 @@ jobs:
# with:
# name: ios-artifacts
# path: |
# Ether/.html/src-tauri/gen/apple/build/**/*.ipa
# @ether/.html/src-tauri/gen/apple/build/**/*.ipa

# ---------------------------------------------------------------------------
# Create GitHub Release with all artifacts
Expand Down
File renamed without changes.
37 changes: 37 additions & 0 deletions @ether/.html/TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
- Upload files
- dropdown of branches
- ..
- Access permissions for which file shown to whom, for .ray is also content of the file
- On editing access permission: prompt with should you change the history access as well?
- Files can inside them have a index.ray.js, to decide how to render them
- Have multiple versions open at the same time.\
- Clones by default go under @ether/<> , how in the interface to say I want @ether @ @ether version is @local
- Version @local is reserved for this. usernames as version are reserved: The @local is relative to you, the @<ANYONE> is the version by someone else under the same name. or @username/other_path similarly, @username versions are reserved.
- fork should disappear the /~/@me when not @ether, any @/# is reserved and cannot be cloned to. But the original path is fine @ether/library for instance.
- Namespaces of versions. so latest has branches main, ..., and again @me has branches main.. / @me~main ; version of the repository vs branch version

Networking, hosting as a peer is opt-in, but using others is done by default, has to be turned off. For function execution on ether servers is agnostic cloud - choose which one default to the calculated cheapest/what according to preferences; redundancy etc.? ; Need to be able to separately turn on peer hosting vs hosting a function endpoint. Might want to do that per object or top-level, so Online turns on @public, need separate keyword for through proxy, so only through @ether for instance. @proxy.@public @proxy being @ether by default. Another setting needs to be turned on to "act as server"; opt-in for the hosting as peer. Allow chaining of that @, to say traffic should be routed like that. Or the folder is hosted under me, but not part of the Index, so @Public works on where it is hosted; so that you dont have to mark @proxy everywhere. Basically instead: "Start hosting this file/endpoints from your local machine" instead.

- PR comments should use chat infrastructure.


- after encrypt delete history of unencrypted prompt


- allow disabling of the UI overlay for index.ray.js


- Chat goes in @ether/@<USER CHATTING WITH>/
- Chat has text information but also 3d info later.
- Chat has index.ray.js on how to render the chat (shadowed/inherited from main chat repo)
- Group gets created in /@<GROUP> or @ether/#{UUID} (can be given a separate #/@ name by reserving it) then a @me version of that chat.
- All the @me versions should be indexed separately in @me


- shadowed files should be low opacity.


- For the iframes they should come with the caller character, which is/isnt allowed to access the certain player information. It's not @local but a different guest or so.


- More intelligent path mapping when the number of files is very large automatically
171 changes: 171 additions & 0 deletions @ether/.html/UI/API.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// ============================================================
// API.ts — Centralized data access facade
// ============================================================
// All consumers import from here instead of DummyData.ts directly.
//
// Every data access wrapper is async and routes through getAPI(),
// which auto-detects the backend (Dummy, Tauri, or Http).

// ---- A) Type re-exports ----
export type { FileEntry, CompoundEntry, TreeEntry, Repository } from './DummyData.ts';
export type { PullRequest, PRStatus, FileDiff, PRCommit, PRComment, ActivityItem, InlinePR, CategoryPRSummary } from './DummyData.ts';

// ---- B) Pure utility re-exports (never become API calls) ----
export { isCompound, flattenEntries, resolveDirectory, resolveFile, resolveFiles } from './DummyData.ts';

// ---- C) EtherAPI — the async data access layer ----
export { getAPI, setAPI } from './EtherAPI.ts';
export type { EtherAPI } from './EtherAPI.ts';

// ---- D) Async data access wrappers (route through getAPI()) ----

import { getAPI } from './EtherAPI.ts';
import type { Repository, InlinePR, CategoryPRSummary, PullRequest } from './DummyData.ts';

export async function getRepository(user: string): Promise<Repository | null> {
return getAPI().getRepository(user);
}

export async function getWorld(user: string, world: string): Promise<Repository | null> {
return getAPI().getWorld(user, world);
}

export async function getReferencedUsers(user: string, world?: string | null): Promise<string[]> {
const users = await getAPI().getReferencedUsers(user, world);
// At the top level (no world context), always include @ether so /@ether is navigable
if (!world && !users.includes('ether')) users.push('ether');
return users;
}

export async function getReferencedWorlds(user: string, world?: string | null): Promise<string[]> {
return getAPI().getReferencedWorlds(user, world);
}

export async function getOpenPRCount(canonicalPath: string): Promise<number> {
return getAPI().getOpenPRCount(canonicalPath);
}

export async function getInlinePullRequests(canonicalPath: string): Promise<InlinePR[]> {
return getAPI().getInlinePullRequests(canonicalPath);
}

export async function getCategoryPRSummary(path: string, prefix: '~' | '@'): Promise<CategoryPRSummary | null> {
return getAPI().getCategoryPRSummary(path, prefix);
}

export async function getCategoryPullRequests(path: string, prefix: '~' | '@'): Promise<InlinePR[]> {
return getAPI().getCategoryPullRequests(path, prefix);
}

export async function getPullRequest(path: string, id: number): Promise<PullRequest | null> {
return getAPI().getPullRequest(path, id);
}

export async function createPullRequest(
canonicalPath: string,
title: string,
description: string,
sourceLabel: string,
targetLabel: string,
author?: string,
): Promise<PullRequest> {
return getAPI().createPullRequest(canonicalPath, title, description, sourceLabel, targetLabel, author || getCurrentPlayer());
}

// ---- E) LocalStorage state ----

// -- Player identity --

export function getCurrentPlayer(): string {
return localStorage.getItem('ether:name') || 'anonymous';
}

export function getDefaultUser(): string {
return getCurrentPlayer();
}

// -- Stars --

const STARS_KEY = 'ether:stars';

function setStars(stars: string[]): void {
localStorage.setItem(STARS_KEY, stars.join('\n'));
}

export function getStars(): string[] {
const raw = localStorage.getItem(STARS_KEY);
return raw ? raw.split('\n').filter(Boolean) : [];
}

export function getStarCount(canonicalPath: string): number {
const raw = localStorage.getItem(`ether:star-count:${canonicalPath}`);
return raw ? parseInt(raw, 10) || 0 : 0;
}

export function setStarCount(canonicalPath: string, count: number): void {
localStorage.setItem(`ether:star-count:${canonicalPath}`, String(Math.max(0, count)));
}

export function isStarred(canonicalPath: string): boolean {
const stars = getStars();
if (stars.includes(canonicalPath)) return true;
// Parent match — but NOT for worlds (#), players (@), or top-level libraries
const parts = canonicalPath.split('/');
for (let i = parts.length - 1; i >= 1; i--) {
const parent = parts.slice(0, i).join('/');
const child = parts[i];
// Stop cascade at world/player/top-level-library boundaries
if (child.startsWith('@') || child.startsWith('#') || child.startsWith('~')) break;
// First real directory after @user or @user/#world = top-level library, needs own star
if (i === 1 || parts[i - 1].startsWith('@') || parts[i - 1].startsWith('#') || parts[i - 1].startsWith('~')) break;
if (stars.includes(parent)) return true;
}
return false;
}

export function toggleStar(canonicalPath: string): boolean {
const stars = getStars();
const idx = stars.indexOf(canonicalPath);
if (idx >= 0) {
stars.splice(idx, 1);
setStars(stars);
return false;
} else {
stars.push(canonicalPath);
setStars(stars);
return true;
}
}

// -- Forks --

export function getForkCount(canonicalPath: string): number {
const raw = localStorage.getItem(`ether:fork-count:${canonicalPath}`);
return raw ? parseInt(raw, 10) || 0 : 0;
}

export function setForkCount(canonicalPath: string, count: number): void {
localStorage.setItem(`ether:fork-count:${canonicalPath}`, String(Math.max(0, count)));
}

// -- Sessions --

function sessionKey(user: string): string {
return `ether:session:${user}`;
}

export function loadSession(user: string): Record<string, any> {
try {
const raw = localStorage.getItem(sessionKey(user));
return raw ? JSON.parse(raw) : {};
} catch { return {}; }
}

export function saveSession(user: string, data: Record<string, any>): void {
localStorage.setItem(sessionKey(user), JSON.stringify(data, null, 2));
}

export function getSessionContent(user: string): string {
const session = loadSession(user);
return JSON.stringify(session, null, 2);
}
Loading