Skip to content
Merged
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
30 changes: 26 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@

## Validation After Changes

After **any** code change, always run these two commands and fix any errors before considering the task done:
After **any** code change, always follow these rules to ensure quality while being efficient:

1. `just validate` — type-checks the TypeScript source without emitting output
2. `just toolbox test-all` — builds and runs all integration tests inside the Fedora toolbox, printing a pass/fail summary
1. **Always run `just validate`** — type-checks the source, lints, and checks formatting. Fix any reported errors.
2. **Run targeted integration tests:**
* If you modified only **one module**, run only the integration test for that module (e.g., `just test tests/shell/auroraTrayIcons.js`).
* If you made **formatting-only changes** (Prettier) and have already passed the tests in a previous turn, you only need to run `just validate`.
* If you made **architectural or cross-cutting changes**, run `just toolbox test-all`.

To read only the relevant output from the test run (pass/fail summary):
**IMPORTANT:** Never execute the `test` command (or `test-all`) chained with another command using `&&`. Always run it as a separate standalone turn.

To read only the relevant output from a `test-all` run (pass/fail summary):
```sh
just toolbox test-all 2>&1 | grep -E "PASS:|FAIL:|Results:"
```
Expand Down Expand Up @@ -160,6 +165,23 @@ return [/* …, */ myModule];
- Keep `enable()` and `disable()` symmetric.
- **Strictly follow Dependency Injection.** No direct imports of `gi://Shell`, `Main`, etc., inside module domain logic.

## Logging Style

Prefix every log message with the module name in `[PascalCase]` brackets. Use the global `logger` from `~/core/logger.ts` — never `console.log/warn` or `GLib.log_structured` directly from module code.

```typescript
import { logger } from '~/core/logger.ts';

// Correct
logger.log('[AuroraTray] Item added: ' + id);

// Wrong
logger.log('[Aurora Shell] [aurora-tray] Item added: ' + id);
console.warn('[aurora-shell] Something failed');
```

The `[Aurora Shell]` prefix is redundant — SYSLOG_IDENTIFIER already routes journal output to the extension.

## Reading GNOME Shell Source

GNOME Shell JS source is embedded in `libshell-XX.so` as a GResource archive. The stylesheet files is `gnome-shell-theme.gresource`. Use `gresource` to read it without needing the source checkout.
Expand Down
18 changes: 18 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,21 @@ All jobs must pass before a PR can be merged.
- **Constants:** `UPPER_CASE`
- **Symmetry:** Everything connected in `enable()` **must** be disconnected or destroyed in `disable()`.
- **Dependency Injection:** Strictly follow DI; do not reach out to globals.

## Logging Style

Always prefix log messages with the module name in `[PascalCase]` brackets. Use the global `logger` from `~/core/logger.ts` for all logging — do not call `console.log/warn` or `GLib.log_structured` directly from module code.

```typescript
import { logger } from '~/core/logger.ts';

// Correct
logger.log('[AuroraTray] Item added: ' + id);
logger.warn('[IconWeave] No match found for ' + wmClass);

// Wrong — redundant prefix, wrong casing, or bypasses logger
logger.log('[Aurora Shell] [aurora-tray] Item added: ' + id);
console.warn('[aurora-shell] Something failed');
```

The `[Aurora Shell]` prefix is redundant: the SYSLOG_IDENTIFIER in structured logs already routes output to the right extension in the journal.
38 changes: 38 additions & 0 deletions data/schemas/org.gnome.shell.extensions.aurora-shell.gschema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,43 @@
<summary>Enable Bluetooth Menu module</summary>
<description>Enhances the Bluetooth Quick Settings panel with battery levels and animated icons</description>
</key>
<key name="module-tray-icons" type="b">
<default>true</default>
<summary>Enable Tray Icons module</summary>
<description>Shows SNI and background app icons in the panel</description>
</key>
<key name="tray-icons-limit" type="i">
<range min="1" max="20"/>
<default>4</default>
<summary>Max visible tray icons</summary>
<description>Number of icons shown before the expand chevron appears</description>
</key>
<key name="tray-icons-icon-size" type="i">
<range min="14" max="24"/>
<default>18</default>
<summary>Tray icon size in pixels</summary>
<description>Size of each tray icon (14–24 px)</description>
</key>
<key name="tray-icons-attention-timeout" type="i">
<range min="1" max="30"/>
<default>5</default>
<summary>Attention auto-collapse timeout (seconds)</summary>
<description>Seconds before auto-collapsing after a NeedsAttention notification</description>
</key>
<key name="tray-icons-dedup-bg-apps" type="b">
<default>true</default>
<summary>Hide background app when SNI icon is present</summary>
<description>When enabled, background app icons are removed from the tray if the same app has an SNI tray icon</description>
</key>
<key name="tray-icons-hide-bg-quick-settings" type="b">
<default>true</default>
<summary>Hide Background Apps from Quick Settings panel</summary>
<description>When enabled, the Background Apps section is hidden from the Quick Settings dropdown</description>
</key>
<key name="tray-icons-recolor-symbolic-pixmaps" type="b">
<default>true</default>
<summary>Recolor symbolic SNI pixmaps</summary>
<description>Automatically recolor monochrome SNI tray pixmaps to match the current panel theme</description>
</key>
</schema>
</schemalist>
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ coverage:
# Wrapped in dbus-run-session to avoid conflicting with any running GNOME session.
# Usage: just test tests/shell/auroraBasic.js
test script: package
dbus-run-session gnome-shell-test-tool --headless \
GSETTINGS_SCHEMA_DIR=/usr/share/glib-2.0/schemas dbus-run-session gnome-shell-test-tool --headless \
--extension dist/target/{{ uuid }}.shell-extension.zip \
{{ script }}

Expand Down
2 changes: 1 addition & 1 deletion metadata.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "Aurora Shell",
"description": "A customizable GNOME Shell extension that enhances the user experience with various modules and features.",
"version": 50.1,
"version-name": "50.1",
"uuid": "aurora-shell@luminusos.github.io",
"url": "https://github.com/luminusOS/aurora-shell",
"settings-schema": "org.gnome.shell.extensions.aurora-shell",
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"prettier:check": "prettier --check \"src/**/*.{ts,scss}\"",
"stylelint": "stylelint \"src/styles/**/*.scss\"",
"watch:css": "tsx sass.config.ts --watch",
"test:unit": "node --import tsx/esm --test tests/unit/metadata.test.ts tests/unit/schema.test.ts tests/unit/registry.test.ts tests/unit/monitorTopology.test.ts",
"test:unit:coverage": "node --import tsx/esm --experimental-test-coverage --test tests/unit/metadata.test.ts tests/unit/schema.test.ts tests/unit/registry.test.ts tests/unit/monitorTopology.test.ts"
"test:unit": "node --import tsx/esm --test tests/unit/metadata.test.ts tests/unit/schema.test.ts tests/unit/registry.test.ts tests/unit/monitorTopology.test.ts tests/unit/trayState.test.ts",
"test:unit:coverage": "node --import tsx/esm --experimental-test-coverage --test tests/unit/metadata.test.ts tests/unit/schema.test.ts tests/unit/registry.test.ts tests/unit/monitorTopology.test.ts tests/unit/trayState.test.ts"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
Expand Down
7 changes: 4 additions & 3 deletions scripts/bump-version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ node -e "
const file = '$METADATA_FILE';
const data = JSON.parse(fs.readFileSync(file, 'utf8'));

data.version = '$VERSION';

data['version-name'] = '$VERSION';
delete data['version'];

// Extract major version (e.g., 50.1 -> 50)
const majorVersion = '$VERSION'.split('.')[0];

// Also optionally update the shell-version array if needed
if (!data['shell-version'].includes(majorVersion)) {
data['shell-version'].push(majorVersion);
Expand Down
3 changes: 0 additions & 3 deletions src/core/context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import GObject from '@girs/gobject-2.0';
import type { Logger } from './logger.ts';
import type { SettingsManager } from './settings.ts';
import type { ShellEnvironment } from './adapters/shell.ts';

Expand All @@ -14,7 +13,6 @@ export class AuroraSignals extends GObject.Object {}
export interface ExtensionContext {
readonly uuid: string;
readonly path: string;
readonly logger: Logger;
readonly settings: SettingsManager;
readonly shell: ShellEnvironment;
readonly signals: AuroraSignals;
Expand All @@ -26,7 +24,6 @@ export class DefaultExtensionContext implements ExtensionContext {
constructor(
public readonly uuid: string,
public readonly path: string,
public readonly logger: Logger,
public readonly settings: SettingsManager,
public readonly shell: ShellEnvironment,
) {
Expand Down
58 changes: 51 additions & 7 deletions src/core/logger.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import GLib from '@girs/glib-2.0';

export type LogOptions = {
prefix?: string;
};

export interface Logger {
log(msg: string, options: LogOptions, ...args: any[]): void;
log(msg: string, ...args: any[]): void;
debug(msg: string, options: LogOptions, ...args: any[]): void;
debug(msg: string, ...args: any[]): void;
info(msg: string, options: LogOptions, ...args: any[]): void;
info(msg: string, ...args: any[]): void;
warn(msg: string, options: LogOptions, ...args: any[]): void;
warn(msg: string, ...args: any[]): void;
error(msg: string, options: LogOptions, ...args: any[]): void;
error(msg: string, ...args: any[]): void;
}

Expand All @@ -17,9 +26,25 @@ export class ConsoleLogger implements Logger {
this._uuid = uuid;
}

private _fmt(msg: string, args: any[]): string {
private _splitArgs(args: any[]): { options: LogOptions; args: any[] } {
const [first, ...rest] = args;
if (this._isLogOptions(first)) return { options: first, args: rest };
return { options: {}, args };
}

private _isLogOptions(value: unknown): value is LogOptions {
return (
typeof value === 'object' &&
value !== null &&
'prefix' in value &&
(value as LogOptions).prefix !== undefined
);
}

private _fmt(msg: string, args: any[], options: LogOptions): string {
const suffix = args.length ? ` ${args.map((a) => String(a)).join(' ')}` : '';
return `[${this._prefix}] ${msg}${suffix}`;
const body = `${msg}${suffix}`;
return options.prefix ? `[${options.prefix}] ${body}` : body;
}

private _emit(level: GLib.LogLevelFlags, msg: string): void {
Expand All @@ -30,22 +55,41 @@ export class ConsoleLogger implements Logger {
}

log(msg: string, ...args: any[]): void {
this._emit(GLib.LogLevelFlags.LEVEL_MESSAGE, this._fmt(msg, args));
const { options, args: rest } = this._splitArgs(args);
this._emit(GLib.LogLevelFlags.LEVEL_MESSAGE, this._fmt(msg, rest, options));
}

debug(msg: string, ...args: any[]): void {
this._emit(GLib.LogLevelFlags.LEVEL_DEBUG, this._fmt(msg, args));
const { options, args: rest } = this._splitArgs(args);
this._emit(GLib.LogLevelFlags.LEVEL_DEBUG, this._fmt(msg, rest, options));
}

info(msg: string, ...args: any[]): void {
this._emit(GLib.LogLevelFlags.LEVEL_MESSAGE, this._fmt(msg, args));
const { options, args: rest } = this._splitArgs(args);
this._emit(GLib.LogLevelFlags.LEVEL_MESSAGE, this._fmt(msg, rest, options));
}

warn(msg: string, ...args: any[]): void {
this._emit(GLib.LogLevelFlags.LEVEL_WARNING, this._fmt(msg, args));
const { options, args: rest } = this._splitArgs(args);
this._emit(GLib.LogLevelFlags.LEVEL_WARNING, this._fmt(msg, rest, options));
}

error(msg: string, ...args: any[]): void {
this._emit(GLib.LogLevelFlags.LEVEL_CRITICAL, this._fmt(msg, args));
const { options, args: rest } = this._splitArgs(args);
this._emit(GLib.LogLevelFlags.LEVEL_CRITICAL, this._fmt(msg, rest, options));
}
}

let _activeLogger: Logger = new ConsoleLogger();

export const logger: Logger = {
log: (...args) => _activeLogger.log(...args),
debug: (...args) => _activeLogger.debug(...args),
info: (...args) => _activeLogger.info(...args),
warn: (...args) => _activeLogger.warn(...args),
error: (...args) => _activeLogger.error(...args),
};

export function setGlobalLogger(l: Logger): void {
_activeLogger = l;
}
24 changes: 13 additions & 11 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import { getModuleRegistry, type ModuleDefinition } from './registry.ts';
import type { ExtensionContext } from '~/core/context.ts';
import { initIcons, cleanupIcons } from '~/shared/icons.ts';
import { DefaultExtensionContext } from '~/core/context.ts';
import { ConsoleLogger } from '~/core/logger.ts';
import { ConsoleLogger, setGlobalLogger, logger } from '~/core/logger.ts';
import { GSettingsManager } from '~/core/settings.ts';
import { GnomeShellAdapter } from '~/core/adapters/shell.ts';

const LOG_PREFIX = 'AuroraShell';

/**
* Aurora Shell Extension
*
Expand All @@ -24,14 +26,14 @@ export default class AuroraShellExtension extends Extension {
private _context: ExtensionContext | null = null;

override enable(): void {
const logger = new ConsoleLogger('Aurora Shell', this.uuid);
logger.log('Enabling extension');
const consoleLogger = new ConsoleLogger('Aurora Shell', this.uuid);
setGlobalLogger(consoleLogger);
consoleLogger.log('Enabling extension', { prefix: LOG_PREFIX });

this._settings = this.getSettings();
this._context = new DefaultExtensionContext(
this.uuid,
this.path,
logger,
new GSettingsManager(this._settings),
new GnomeShellAdapter(),
);
Expand All @@ -55,7 +57,7 @@ export default class AuroraShellExtension extends Extension {
try {
module.enable();
} catch (e) {
this._context!.logger.error(`Failed to enable module ${name}: ${e}`);
logger.error(`Failed to enable module ${name}: ${e}`, { prefix: LOG_PREFIX });
}
}
}
Expand All @@ -79,35 +81,35 @@ export default class AuroraShellExtension extends Extension {
const existing = this._modules.get(def.key);

if (enabled && !existing) {
this._context!.logger.log(`Enabling module ${def.key}`);
logger.log(`Enabling module ${def.key}`, { prefix: LOG_PREFIX });
try {
const module = def.factory(this._context!);
module.enable();
this._modules.set(def.key, module);
} catch (e) {
this._context!.logger.error(`Failed to enable module ${def.key}: ${e}`);
logger.error(`Failed to enable module ${def.key}: ${e}`, { prefix: LOG_PREFIX });
}
} else if (!enabled && existing) {
this._context!.logger.log(`Disabling module ${def.key}`);
logger.log(`Disabling module ${def.key}`, { prefix: LOG_PREFIX });
try {
existing.disable();
this._modules.delete(def.key);
} catch (e) {
this._context!.logger.error(`Failed to disable module ${def.key}: ${e}`);
logger.error(`Failed to disable module ${def.key}: ${e}`, { prefix: LOG_PREFIX });
}
}
}

override disable(): void {
this._context!.logger.log('Disabling extension');
logger.log('Disabling extension', { prefix: LOG_PREFIX });

this._settings?.disconnectObject(this);

for (const [name, module] of this._modules) {
try {
module.disable();
} catch (e) {
this._context!.logger.error(`Failed to disable module ${name}: ${e}`);
logger.error(`Failed to disable module ${name}: ${e}`, { prefix: LOG_PREFIX });
}
}

Expand Down
8 changes: 5 additions & 3 deletions src/modules/autoThemeSwitcher/autoThemeSwitcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import GLib from '@girs/glib-2.0';
import Gio from '@girs/gio-2.0';

import type { ExtensionContext } from '~/core/context.ts';
import { logger } from '~/core/logger.ts';
import { Module } from '~/module.ts';
import type { SettingsManager } from '~/core/settings.ts';
import type { ModuleDefinition } from '~/module.ts';
Expand All @@ -23,6 +24,7 @@ const TIME_KEYS = [
'auto-theme-switcher-dark-hours',
'auto-theme-switcher-dark-minutes',
] as const;
const LOG_PREFIX = 'AutoThemeSwitcher';

export class AutoThemeSwitcher extends Module {
private _sourceId: number | null = null;
Expand Down Expand Up @@ -62,7 +64,7 @@ export class AutoThemeSwitcher extends Module {
);
this._tick();
} catch (error) {
this.context.logger.error('AutoThemeSwitcher: Failed to enable:', error);
logger.error('Failed to enable:', { prefix: LOG_PREFIX }, error);
}
}

Expand Down Expand Up @@ -106,9 +108,9 @@ export class AutoThemeSwitcher extends Module {

if (current_scheme !== scheme) {
this._desktopSettings.setString('color-scheme', scheme);
this.context.logger.debug(`AutoThemeSwitcher: applied ${scheme}`);
logger.debug(`applied ${scheme}`, { prefix: LOG_PREFIX });
} else {
this.context.logger.debug(`AutoThemeSwitcher: already on ${scheme}`);
logger.debug(`already on ${scheme}`, { prefix: LOG_PREFIX });
}

let next = isLight ? dark : light;
Expand Down
Loading
Loading