Steven
Steven6 min de lectura

Una base de codi, dues apps: com fer white-label sense fork

GeekBye i Pavleur són dues apps d'escriptori amb marques diferents construïdes des d'un únic repositori — sense fork, sense base de codi duplicada. Aquesta és la maquinària de build que fa que una sola base de codi compili en dos productes, i l'error d'una sola línia que va fer que la nostra segona app es presentés amb el nom equivocat.

Engineering
Arquitectura
Build
GeekBye Releases
Una base de codi, dues apps: com fer white-label sense fork

GeekBye és un notetaker de reunions. Pavleur és una app de pràctica d'entrevistes amb IA. Semblen dos productes diferents perquè són dos productes diferents — però estan construïts des d'una sola base de codi, sense fork i sense cap branca que es vagi desviant. Aquest és el costat d'enginyeria del white-labeling: no "per què vendre una app amb la teva marca" (això és una altra entrada), sinó com compiles realment un únic repositori en dues apps — i l'error gens glamurós que s'amaga en tot sistema white-label fins que el vas a buscar.

Genera, no facis branques

La manera equivocada de fer funcionar dos productes des d'una base de codi és escampar if (product === 'pavleur') per tota l'app, o fer un fork i resar perquè les dues còpies no divergeixin mai. Totes dues opcions es podreixen de pressa.

L'enfocament de GeekBye és la generació de codi en temps de build. Cada producte té un fitxer de configuració — configs/geekbye/config.ts, configs/pavleur/config.ts — que en conté la identitat: nom, bundle id, protocol d'URL, el repositori de releases per a les actualitzacions automàtiques, i així successivament. Abans d'una build, s'executa un sol script:

node scripts/build-product.js pavleur

Aquest script llegeix la config del producte triat i fa tres coses concretes:

  1. Genera dos mòduls de configuració — un per al renderer (src/config/product.ts), un per al procés principal (electron/config/product.ts) — cadascun estampat amb una capçalera "DO NOT EDIT MANUALLY — changes will be overwritten". La resta de l'app simplement importa productName, applicationId i protocol de la config i no pensa mai en quina marca és.
  2. Copia només els assets d'aquella marca — les icones, el logo petit, la icona de les notificacions toast. Una build de GeekBye conté físicament zero art de Pavleur i viceversa. Res per filtrar.
  3. Reescriu package.json (a les builds de release) — l'app id, el nom del producte, el protocol d'URL, el repo de releases de GitHub on publicar i, com a detall agradable, el text de permís de micròfon amb la marca, perquè macOS pregunti "Pavleur voldria accedir al micròfon", no la marca equivocada.

Tota l'app és agnòstica de marca; la marca s'injecta en temps de build. No hi ha cap commutador de producte en temps d'execució que es pugui equivocar, perquè en temps d'execució només hi ha mai un producte incrustat.

Un backend, dues marques

Totes dues apps parlen amb el mateix backend. Llavors, com sap el servidor a quin producte — i a quins preus, prompts i límits — pertany una petició?

Dos senyals, tots dos fixats en temps de build. Primer, un application id (geekbye o pavleur) incrustat a la config i enviat amb les peticions, perquè el backend pugui encaminar cap al comportament del producte correcte. Segon, un User-Agent que el client adjunta a cada crida al backend, amb la forma Product/version (platform) — per exemple Pavleur/1.2.5 (win32) o GeekBye/1.7.3 (darwin). Aquesta única capçalera dona al backend atribució per producte i per sistema operatiu de franc, sense cap camp d'analítica a part. Les actualitzacions automàtiques, mentrestant, es mantenen aïllades: cada producte publica al seu propi repositori de releases, així que una actualització de GeekBye mai no es pot servir a una instal·lació de Pavleur.

L'onboarding també difereix per producte — l'assistent de GeekBye va de transcripció de reunions, el de Pavleur de pràctica d'entrevistes — i el correcte es tria en temps d'execució a partir de l'únic application id incrustat. La mateixa maquinària, dues portes d'entrada.

L'error: quan Pavleur es va presentar com a GeekBye

Aquí ve la batalleta, i és la que tot sistema white-label té en alguna versió.

La generació de config funcionava. Les icones s'intercanviaven. El prompt del micròfon duia la marca. Els protocols i els bundle ids eren correctes. Segons qualsevol mesura visible, una build de Pavleur era Pavleur. I tot i així, durant un temps, cada build de Pavleur trucava a casa al backend presentant-se com a GeekBye.

El culpable era una sola cadena hardcodejada, i s'amagava al lloc menys glamurós imaginable: el constructor del User-Agent, ficat dins d'un helper de reintents de xarxa. Tot el que era de cara a l'usuari s'havia encaminat a través de la config, però aquesta utilitat de fontaneria profunda encara tenia un literal `GeekBye/${version}` a dins. Ningú no pensa en una utilitat de reintents com a "branding", així que ningú no l'havia revisada. La solució eren quatre línies — importar el productName dinàmic de la config i interpolar-lo en lloc de la marca hardcodejada — però la lliçó és tot el sentit d'això:

El white-labeling no és "ha canviat el logo?". És "passa cada cadena d'identitat per la config?" I els últims reductes mai no viuen a la UI, on aniries a mirar. Viuen al User-Agent, al gestor del protocol de deep links, als textos dels prompts de permisos, als missatges d'error — la fontaneria que ningú no arxiva sota "marca". L'única defensa fiable és tractar el literal de la marca com una cosa que caces activament: fes grep del nom hardcodejat a tota la base de codi i fes fallar la build si apareix enlloc fora de la config que se suposa que el defineix.

Tres coses que ens va ensenyar el white-labeling

  1. Genera, no facis branques. La codegen en temps de build de dos petits mòduls de configuració més la còpia selectiva d'assets guanya tant al fork com a un matoll de comprovacions de producte en temps d'execució. L'app es manté agnòstica de marca; la marca és un input de build.
  2. Les cadenes d'identitat es filtren cap a la fontaneria. La UI és el 90% fàcil. El 10% difícil és el User-Agent, el protocol d'URL, la cadena del permís de micròfon, els textos d'error. Fes-los passar tots per un únic export productName / applicationId — i fes grep del nom de marca cru com a porta de CI perquè un literal perdut no pugui arribar mai a producció.
  3. Un backend, moltes marques, desambiguades per id i capçalera. Un application id de temps de build més un User-Agent Product/version (platform) donen atribució per producte i per plataforma amb zero infraestructura duplicada — separant netament la identitat immutable de temps de build del comportament en temps d'execució.

Per al costat de producte i privadesa de fer funcionar un notetaker white-label sota la teva pròpia marca, vegeu el notetaker d'IA white-label. Aquest és el sisè capítol de la història de la v1 que es converteix en GeekBye v2 — per al capítol anterior, què és realment una release de 127 commits (v1.7.0); i per a tot l'arc, l'anatomia de publicar programari fins a la perfecció.

Articles Relacionats

Què és realment una release de 127 commits
Steven
Steven6 min de lectura

Què és realment una release de 127 commits

GeekBye v1.7.0 van ser 127 commits en onze dies. Des de fora, això sembla un centenar de coses petites. Des de dins van ser dues grans funcionalitats entrellaçades al final — i una d'elles es va construir al lloc equivocat, i després es va arrencar i reconstruir a mitja release. Aquesta és l'anatomia d'una gran release.

Engineering
Release
Arquitectura
Vam esborrar 5.000 línies de codi d'àudio — i les transcripcions van començar a aparèixer duplicades
Steven
Steven7 min de lectura

Vam esborrar 5.000 línies de codi d'àudio — i les transcripcions van començar a aparèixer duplicades

GeekBye v1.6 va arrencar dos transcriptors Swift al dispositiu i els va substituir per una única canonada unificada — una eliminació neta de més de 5.000 línies, feta mentre la gent era enmig d'una reunió a l'aplicació. Llavors les transcripcions van començar a aparèixer dues vegades, perquè l'error va migrar a l'única capa que encara pensava que hi havia dos motors.

Enginyeria
Transcripció
Arquitectura
Una connexió perduda no hauria de fer petar tota l'aplicació — però la nostra ho feia
Steven
Steven7 min de lectura

Una connexió perduda no hauria de fer petar tota l'aplicació — però la nostra ho feia

Quan el nostre backend es va desconnectar en plena reunió, no només va aturar la transcripció: va fer petar tota l'aplicació. La causa era un únic esdeveniment sense gestionar, i la solució eren deu línies. Aquest és el grup de versions que va fer que GeekBye es mantingués amb la sessió iniciada i connectat a través de les coses que abans el mataven.

Enginyeria
Fiabilitat
Electron