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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ jobs:
cache: npm
- run: npm ci
- run: npm run verify
- run: git diff --exit-code -- packages/orbit-tags/runtime/dist/injected.js
- run: git diff --exit-code -- packages/orbit-tags/runtime/dist/injected.js packages/orbit-blur/runtime/dist/injected.js
- run: npm run test:package
16 changes: 14 additions & 2 deletions docs/source-layout.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Source structure and dependency rules

OrbitAI uses two independently versioned packages. Directory boundaries describe execution environments and ownership; they are not additional npm packages. `npm run check` validates imports using a TypeScript-capable AST parser and rejects runtime import cycles.
OrbitAI uses independently versioned packages. Directory boundaries describe execution environments and ownership; they are not additional npm packages. `npm run check` validates imports using a TypeScript-capable AST parser and rejects runtime import cycles.

## Orbit

Expand Down Expand Up @@ -63,9 +63,21 @@ packages/orbit-tags/

Orbit snapshots the business package without flattening its compiled hierarchy. The plugin manifest loads `dist/runtime/src/service/activate.js`; the diagnostic controller does not write platform configuration. Tags uses module ID `orbit-tags`; its package snapshot records the assigned data directory. See [installation ownership](orbit-platform-design.md).

## Orbit Blur

```text
packages/orbit-blur/
├── src/ # Renderer TypeScript (style injection)
├── scripts/build.mjs # esbuild IIFE → runtime/dist/injected.js
├── runtime/dist/injected.js # Tracked browser bundle
└── orbit-plugin.json # Manifest (renderer-only, no service)
```

Renderer-only plugin: transparent backgrounds plus `backdrop-filter` blur. It imports type-only `@c0sc0s/orbit/sdk`. No private Codex selectors and no Orbit implementation imports.

## Verification and change discipline

- `npm run build`: clean only each package's generated Node directory, build Orbit first, then Tags and its browser bundle.
- `npm run build`: clean only each package's generated Node directory, build Orbit first, then Tags, then Blur and its browser bundle.
- `npm run check`: syntax, package boundaries, source-layer dependencies, runtime cycles and documentation links.
- `npm run typecheck`: strict implementation checks and public SDK contract tests.
- Browser typechecks explicitly exclude Node ambient types, preventing accidental use of Node globals in renderer code.
Expand Down
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"node": ">=22.13.0"
},
"scripts": {
"build": "npm run build -w @c0sc0s/orbit && npm run build -w @c0sc0s/orbit-tags",
"build": "npm run build -w @c0sc0s/orbit && npm run build -w @c0sc0s/orbit-tags && npm run build -w @c0sc0s/orbit-blur",
"check": "node scripts/check.mjs && node scripts/check-architecture.mjs",
"typecheck": "npm run typecheck --workspaces --if-present",
"test": "node --test scripts/test/*.test.mjs && npm test --workspaces --if-present",
Expand Down
11 changes: 11 additions & 0 deletions packages/orbit-blur/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# @c0sc0s/orbit-blur

Renderer-only Orbit plugin. Injects transparent page backgrounds and `backdrop-filter` Gaussian blur on Codex chrome.

```sh
npm run build -w @c0sc0s/orbit-blur
orbit plugin add ./packages/orbit-blur
orbit plugin enable orbit-blur
```

Bump `runtimeVersion` in `orbit-plugin.json` (and `RUNTIME_VERSION` in source) after injected changes, then rebuild and re-add/apply. The tracked bundle is `runtime/dist/injected.js`.
8 changes: 8 additions & 0 deletions packages/orbit-blur/orbit-plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"schemaVersion": 1,
"id": "orbit-blur",
"version": "0.1.0",
"runtimeVersion": "1.0.0",
"orbit": { "min": "0.5.0", "maxExclusive": "0.6.0" },
"renderer": "runtime/dist/injected.js"
}
34 changes: 34 additions & 0 deletions packages/orbit-blur/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@c0sc0s/orbit-blur",
"version": "0.1.0",
"description": "Orbit plugin: transparent Codex chrome with Gaussian blur.",
"type": "module",
"files": [
"orbit-plugin.json",
"runtime/dist/",
"README.md"
],
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
},
"license": "UNLICENSED",
"repository": {
"type": "git",
"url": "git+https://github.com/c0sc0s/codex-tags.git"
},
"scripts": {
"build": "node scripts/build.mjs",
"prepack": "npm run build",
"test": "npm run build && node --test test/*.test.mjs",
"typecheck": "tsc --noEmit -p tsconfig.json"
},
"engines": {
"node": ">=22"
},
"devDependencies": {
"@c0sc0s/orbit": "0.5.2",
"esbuild": "0.28.2",
"typescript": "7.0.2"
}
}
125 changes: 125 additions & 0 deletions packages/orbit-blur/runtime/dist/injected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"use strict";
var CodexPlugin = (() => {
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/entry.ts
var entry_exports = {};
__export(entry_exports, {
activate: () => activate
});

// src/constants.ts
var RUNTIME_VERSION = "1.0.0";
var PLUGIN_ID = "orbit-blur";
var STYLE_ID = "orbit-blur-style";
var DEFAULT_BLUR_PX = 18;
var DEFAULT_TINT_OPACITY = 0.42;

// src/styles.ts
function clamp(value, min, max) {
return Math.min(max, Math.max(min, value));
}
function numberOption(value, fallback) {
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
}
function resolveBlurSettings(config = {}) {
return {
blurPx: clamp(numberOption(config.blurPx, DEFAULT_BLUR_PX), 0, 64),
tintOpacity: clamp(numberOption(config.tintOpacity, DEFAULT_TINT_OPACITY), 0, 1)
};
}
function buildBlurStyles({ blurPx, tintOpacity }) {
const tintPercent = Math.round(tintOpacity * 100);
return `
:root {
--orbit-blur-px: ${blurPx}px;
--orbit-blur-tint: color-mix(in srgb, Canvas ${tintPercent}%, transparent);
--orbit-blur-shell: color-mix(in srgb, Canvas ${Math.max(12, Math.round(tintPercent * 0.55))}%, transparent);
}

html, body {
background: transparent !important;
background-color: transparent !important;
}

:root {
--color-background: transparent;
--color-background-primary: transparent;
--color-bg: transparent;
--color-bg-primary: transparent;
--background: transparent;
--color-token-background: transparent;
}

aside,
nav,
header,
[role="navigation"],
[role="banner"],
[role="complementary"] {
background-color: var(--orbit-blur-tint) !important;
backdrop-filter: blur(var(--orbit-blur-px)) saturate(1.15);
-webkit-backdrop-filter: blur(var(--orbit-blur-px)) saturate(1.15);
}

#root,
#app,
body > div:first-of-type {
background-color: var(--orbit-blur-shell) !important;
backdrop-filter: blur(var(--orbit-blur-px));
-webkit-backdrop-filter: blur(var(--orbit-blur-px));
}

[role="dialog"],
[aria-modal="true"] {
backdrop-filter: blur(calc(var(--orbit-blur-px) * 0.65)) saturate(1.1);
-webkit-backdrop-filter: blur(calc(var(--orbit-blur-px) * 0.65)) saturate(1.1);
}
`.trim();
}

// src/entry.ts
function ensureStyleElement() {
const existing = document.getElementById(STYLE_ID);
if (existing instanceof HTMLStyleElement) return existing;
const style = document.createElement("style");
style.id = STYLE_ID;
(document.head ?? document.documentElement).appendChild(style);
return style;
}
async function activate(context) {
const settings = resolveBlurSettings(context.config);
const style = ensureStyleElement();
style.textContent = buildBlurStyles(settings);
context.onDispose(() => {
document.getElementById(STYLE_ID)?.remove();
});
return {
isActive: () => Boolean(document.getElementById(STYLE_ID)),
status: () => ({
plugin: PLUGIN_ID,
runtimeVersion: RUNTIME_VERSION,
active: Boolean(document.getElementById(STYLE_ID)),
blurPx: settings.blurPx,
tintOpacity: settings.tintOpacity
})
};
}
return __toCommonJS(entry_exports);
})();
36 changes: 36 additions & 0 deletions packages/orbit-blur/scripts/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { build } from "esbuild";
import { mkdir } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)));
const outputDirectory = join(projectRoot, "runtime", "dist");
const testOutputDirectory = join(projectRoot, "dist");
await mkdir(outputDirectory, { recursive: true });
await mkdir(testOutputDirectory, { recursive: true });

await build({
entryPoints: [join(projectRoot, "src", "entry.ts")],
outfile: join(outputDirectory, "injected.js"),
bundle: true,
format: "iife",
globalName: "CodexPlugin",
platform: "browser",
target: "chrome120",
minify: false,
sourcemap: false,
legalComments: "none",
});

// ESM helpers for Node tests (gitignored under packages/*/dist/).
await build({
entryPoints: [join(projectRoot, "src", "styles.ts")],
outfile: join(testOutputDirectory, "styles.mjs"),
bundle: true,
format: "esm",
platform: "neutral",
target: "es2022",
minify: false,
sourcemap: false,
legalComments: "none",
});
9 changes: 9 additions & 0 deletions packages/orbit-blur/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/** Injected bundle cachebuster; keep in sync with orbit-plugin.json runtimeVersion. */
export const RUNTIME_VERSION = "1.0.0";
export const PLUGIN_ID = "orbit-blur";
export const STYLE_ID = "orbit-blur-style";

/** Soft glass: enough blur to read as frosted without smearing text. */
export const DEFAULT_BLUR_PX = 18;
/** Canvas tint opacity for frosted chrome (0–1). */
export const DEFAULT_TINT_OPACITY = 0.42;
31 changes: 31 additions & 0 deletions packages/orbit-blur/src/entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { RendererContext, RendererPlugin } from "@c0sc0s/orbit/sdk";
import { PLUGIN_ID, RUNTIME_VERSION, STYLE_ID } from "./constants.js";
import { buildBlurStyles, resolveBlurSettings } from "./styles.js";

function ensureStyleElement(): HTMLStyleElement {
const existing = document.getElementById(STYLE_ID);
if (existing instanceof HTMLStyleElement) return existing;
const style = document.createElement("style");
style.id = STYLE_ID;
(document.head ?? document.documentElement).appendChild(style);
return style;
}

export async function activate(context: RendererContext): Promise<RendererPlugin> {
const settings = resolveBlurSettings(context.config);
const style = ensureStyleElement();
style.textContent = buildBlurStyles(settings);
context.onDispose(() => {
document.getElementById(STYLE_ID)?.remove();
});
return {
isActive: () => Boolean(document.getElementById(STYLE_ID)),
status: () => ({
plugin: PLUGIN_ID,
runtimeVersion: RUNTIME_VERSION,
active: Boolean(document.getElementById(STYLE_ID)),
blurPx: settings.blurPx,
tintOpacity: settings.tintOpacity,
}),
};
}
Loading
Loading