feat: rebrand to Resenhacord with a new logo, and post a notice when a message is pinned
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
OpenSSF Scorecard / Scorecard analysis (push) Canceled after 0s
Rebrand. The product name is now Resenhacord everywhere a person reads it:
window title, tray, notifications, About panel, PWA manifest, page title,
recovery and instance-picker screens, and the four i18n strings that named
the app. Code identifiers (BackspaceElectronAPI, @backspace/* workspace
names, the federation protocol) are unchanged — they are not a brand, and
renaming them buys nothing but churn. AGPL notices and upstream attribution
stay as they were.
Two rename details that would each have hurt real users:
- electron-builder's productName changed but appId did not. NSIS keys the
installation on appId, so the updater replaces the Backspace install in
place instead of leaving a second app beside it.
- userData is keyed on app name, so the rename would have moved every
install to an empty profile — logged out, instance URL gone. main.ts now
walks a migration chain ("@backspace/desktop" → "Backspace" →
"Resenhacord"), each step a no-op once its source is gone, and one whose
target already holds data leaves it alone. Autostart lookups accept the
old registry entry name too.
Logo. Two speech bubbles in conversation, violet behind, rose in front with
three dots — the front one is talking. Sources are hand-written SVG in
assets/brand (square viewBox; the gap between bubbles is a mask, not paint,
so the alpha-only macOS template keeps the silhouette). PNG sources are
rendered from the icon SVG and the whole set — .icns, .ico, Linux PNGs, tray
variants, favicons, PWA, maskable — comes from the existing gen-icons
pipeline unchanged.
Pin notice. Pinning a message now posts "X pinned a message to this channel.
See all pinned messages." into the channel, the way Discord does, with two
links: the message itself (jumps to it) and the pins panel. Space messages
had no `type` column — only DM messages carried system events — so this adds
one (migration 0018, default 'user', verified against a copy of the
production database: 88 rows, all 'user'). The route inserts a type='system'
row alongside the pin and broadcasts it as message_created, so the client
renders it with the SystemMessage renderer it already has. Unpinning posts
nothing; it is housekeeping, not news. System notices are excluded from the
notification toast and the message sound — they are not from anyone.
The pins panel and jump target are local state in MainContent; a notice deep
in the timeline reaches them through two small uiStore requests (a counter
and an id), neither persisted.
Verified: web build, 506 web tests (2 new), server typecheck, 804 server
tests, desktop typecheck.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Before Width: | Height: | Size: 364 KiB After Width: | Height: | Size: 364 KiB |
|
Before Width: | Height: | Size: 368 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 955 B After Width: | Height: | Size: 712 B |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 368 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 4.4 KiB |
@@ -1,5 +1,5 @@
|
||||
appId: com.backspace.desktop
|
||||
productName: Backspace
|
||||
productName: Resenhacord
|
||||
artifactName: "${productName}-${version}-${arch}.${ext}"
|
||||
directories:
|
||||
output: dist-electron
|
||||
@@ -36,7 +36,7 @@ publish:
|
||||
- provider: generic
|
||||
url: https://git.resenha.website/devsyncwrld/backspace/releases/download/latest/
|
||||
protocols:
|
||||
- name: Backspace
|
||||
- name: Resenhacord
|
||||
schemes:
|
||||
- backspace
|
||||
mac:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "1.5.0",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
"description": "Backspace",
|
||||
"description": "Resenhacord",
|
||||
"author": {
|
||||
"name": "Jannis Braun",
|
||||
"email": "backspace@backspace.chat"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Backspace — Connect</title>
|
||||
<title>Resenhacord — Conectar</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap');
|
||||
|
||||
@@ -270,7 +270,7 @@
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<div class="header">
|
||||
<h1>Welcome to Backspace</h1>
|
||||
<h1>Bem-vindo ao Resenhacord</h1>
|
||||
<p>Connect to your instance</p>
|
||||
</div>
|
||||
|
||||
@@ -360,7 +360,7 @@
|
||||
// Update header copy to reflect the "switch" intent rather than the
|
||||
// first-run "welcome" framing.
|
||||
document.querySelector('.header h1').textContent = 'Switch instance';
|
||||
document.querySelector('.header p').textContent = 'Connect to a different Backspace instance, or cancel to stay.';
|
||||
document.querySelector('.header p').textContent = 'Conecte-se a outra instância, ou cancele para ficar nesta.';
|
||||
}
|
||||
}
|
||||
init();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Backspace — Recovery</title>
|
||||
<title>Resenhacord — Recuperação</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
@@ -128,7 +128,7 @@
|
||||
<div class="content">
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<h1>Backspace couldn't load.</h1>
|
||||
<h1>O Resenhacord não conseguiu abrir.</h1>
|
||||
<p class="hint" id="hint">Loading…</p>
|
||||
|
||||
<div class="actions">
|
||||
@@ -147,7 +147,7 @@
|
||||
<pre id="diagnostic">loading…</pre>
|
||||
</details>
|
||||
|
||||
<button class="action subtle" id="btn-quit">Quit Backspace</button>
|
||||
<button class="action subtle" id="btn-quit">Fechar o Resenhacord</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -270,7 +270,7 @@
|
||||
|
||||
async function init() {
|
||||
if (!window.backspace) {
|
||||
$('hint').textContent = 'Recovery bridge unavailable. Please quit and reinstall Backspace.';
|
||||
$('hint').textContent = 'A ponte de recuperação não respondeu. Feche e reinstale o Resenhacord.';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 983 B After Width: | Height: | Size: 746 B |
|
Before Width: | Height: | Size: 703 B After Width: | Height: | Size: 474 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 836 B |
@@ -40,24 +40,31 @@ import {
|
||||
import { migrateUserData } from './userDataMigration';
|
||||
|
||||
// Override Electron's package.json-derived app name so userData lives at
|
||||
// "<appData>/Backspace" instead of leaking the monorepo's "@backspace/desktop"
|
||||
// "<appData>/Resenhacord" instead of leaking the monorepo's "@backspace/desktop"
|
||||
// package name. Must run before any app.getPath('userData') consumer.
|
||||
app.setName('Backspace');
|
||||
app.setName('Resenhacord');
|
||||
|
||||
// One-time migration from the historical scoped path. After the move the old
|
||||
// folder is gone, so subsequent launches hit the old-missing no-op branch.
|
||||
// One-time migrations, oldest first, so an install that skipped a release
|
||||
// still walks the whole chain: "@backspace/desktop" → "Backspace" →
|
||||
// "Resenhacord". Each step is a no-op once its source folder is gone, and a
|
||||
// step whose target already has data leaves it alone (`new-populated`), so a
|
||||
// user who already ran the renamed build is never overwritten by stale files.
|
||||
// Without the second step the rename would have logged everyone out: the
|
||||
// instance URL and session live under userData.
|
||||
{
|
||||
const appDataDir = app.getPath('appData');
|
||||
const oldParent = path.join(appDataDir, '@backspace');
|
||||
const result = migrateUserData({
|
||||
oldDir: path.join(oldParent, 'desktop'),
|
||||
newDir: path.join(appDataDir, 'Backspace'),
|
||||
oldParent,
|
||||
});
|
||||
if (result.kind === 'migrated') {
|
||||
console.log(`[userData] migrated ${result.from} → ${result.to}`);
|
||||
} else if (result.kind === 'failed') {
|
||||
console.error('[userData] migration failed:', result.error);
|
||||
const legacyParent = path.join(appDataDir, '@backspace');
|
||||
const steps: { oldDir: string; newDir: string; oldParent: string }[] = [
|
||||
{ oldDir: path.join(legacyParent, 'desktop'), newDir: path.join(appDataDir, 'Backspace'), oldParent: legacyParent },
|
||||
{ oldDir: path.join(appDataDir, 'Backspace'), newDir: path.join(appDataDir, 'Resenhacord'), oldParent: appDataDir },
|
||||
];
|
||||
for (const step of steps) {
|
||||
const result = migrateUserData(step);
|
||||
if (result.kind === 'migrated') {
|
||||
console.log(`[userData] migrated ${result.from} → ${result.to}`);
|
||||
} else if (result.kind === 'failed') {
|
||||
console.error('[userData] migration failed:', result.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +248,7 @@ function applyLoginItemSettings(openAtLogin: boolean, startMinimized: boolean):
|
||||
enabled: openAtLogin,
|
||||
path: process.execPath,
|
||||
args: startMinimized ? ['--hidden'] : [],
|
||||
name: 'Backspace',
|
||||
name: 'Resenhacord',
|
||||
});
|
||||
} else {
|
||||
// Linux: setLoginItemSettings creates ~/.config/autostart/<name>.desktop.
|
||||
@@ -437,7 +444,7 @@ function createWindow(): void {
|
||||
: {}),
|
||||
minWidth: 940,
|
||||
minHeight: 500,
|
||||
title: 'Backspace',
|
||||
title: 'Resenhacord',
|
||||
icon: path.join(__dirname, '..', 'build', 'icon.png'),
|
||||
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'hidden',
|
||||
...(process.platform !== 'darwin' ? {
|
||||
@@ -570,7 +577,7 @@ function createTray(): void {
|
||||
const icon = loadTrayIcon();
|
||||
tray = new Tray(icon);
|
||||
|
||||
tray.setToolTip('Backspace');
|
||||
tray.setToolTip('Resenhacord');
|
||||
// Context menu is set by the recoveryStore subscriber in app.whenReady,
|
||||
// which keeps the menu in sync with update/recovery state.
|
||||
|
||||
@@ -710,7 +717,7 @@ function registerIpcHandlers(): void {
|
||||
// executableWillLaunchAtLogin to honour Task Manager's StartupApproved state.
|
||||
const osState = app.getLoginItemSettings({ path: process.execPath });
|
||||
const ownEntry = osState.launchItems?.find(
|
||||
(item) => item.name === 'Backspace' || item.path?.toLowerCase() === process.execPath.toLowerCase(),
|
||||
(item) => item.name === 'Resenhacord' || item.name === 'Backspace' || item.path?.toLowerCase() === process.execPath.toLowerCase(),
|
||||
);
|
||||
// When the Run entry exists, its args are the source of truth for startMinimized.
|
||||
// When the entry is absent (autostart is off), there is no OS state to read, so we
|
||||
@@ -745,7 +752,7 @@ function registerIpcHandlers(): void {
|
||||
if (process.platform === 'win32') {
|
||||
const osState = app.getLoginItemSettings({ path: process.execPath });
|
||||
const ownEntry = osState.launchItems?.find(
|
||||
(item) => item.name === 'Backspace' || item.path?.toLowerCase() === process.execPath.toLowerCase(),
|
||||
(item) => item.name === 'Resenhacord' || item.name === 'Backspace' || item.path?.toLowerCase() === process.execPath.toLowerCase(),
|
||||
);
|
||||
currentOpenAtLogin = osState.executableWillLaunchAtLogin ?? false;
|
||||
// See `get-auto-launch-settings`: when the entry is absent, fall back to disk
|
||||
@@ -853,7 +860,7 @@ function initAutoUpdater(): void {
|
||||
// (recovery mode) is visible — no need to also fire a native toast.
|
||||
if (!mainWindow?.isFocused()) {
|
||||
showNotification(
|
||||
'Backspace update ready',
|
||||
'Resenhacord update ready',
|
||||
`Click to restart and install version ${version}.`,
|
||||
() => autoUpdater.quitAndInstall(),
|
||||
);
|
||||
@@ -1104,7 +1111,7 @@ if (!gotTheLock) {
|
||||
|
||||
// AGPL-3.0 § 13: native About panel advertises the version + source repo.
|
||||
app.setAboutPanelOptions({
|
||||
applicationName: 'Backspace',
|
||||
applicationName: 'Resenhacord',
|
||||
applicationVersion: app.getVersion(),
|
||||
copyright: `AGPL-3.0-only · Source: ${UPSTREAM_SOURCE_URL}`,
|
||||
website: UPSTREAM_SOURCE_URL,
|
||||
|
||||
@@ -166,7 +166,7 @@ describe('buildTrayMenuTemplate', () => {
|
||||
it('includes Show/Hide/Change Instance/Quit base items', () => {
|
||||
const items = buildTrayMenuTemplate(defaultState());
|
||||
const labels = items.map((i) => i.label);
|
||||
expect(labels).toContain('Show Backspace');
|
||||
expect(labels).toContain('Mostrar o Resenhacord');
|
||||
expect(labels).toContain('Hide');
|
||||
expect(labels).toContain('Change Instance');
|
||||
expect(labels).toContain('Quit');
|
||||
|
||||
@@ -122,7 +122,7 @@ export function buildTrayMenuTemplate(
|
||||
actions?: Partial<MenuActions>,
|
||||
): MenuItemConstructorOptions[] {
|
||||
const items: MenuItemConstructorOptions[] = [
|
||||
{ label: 'Show Backspace', click: actions?.onShow },
|
||||
{ label: 'Mostrar o Resenhacord', click: actions?.onShow },
|
||||
{ label: 'Hide', click: actions?.onHide },
|
||||
{ type: 'separator' },
|
||||
checkForUpdatesItem(state, () => actions?.onCheckForUpdates?.()),
|
||||
|
||||