
Koosoleku printimine PDF-i ilma PDF-teegita
GeekBye ekspordib koosoleku PDF-ina, ja koodis pole kusagil PDF-teeki. See renderdab HTML-i nähtamatus brauseriaknas ja prindib selle. See valik on kogu lugu: see tegi funktsiooni lihtsaks ehitada ja andis sellele iga tõrke, mis päris brauseril on — valge välgatuse, URL-i pikkuse piirangu ja leheküljemurru, mis lõikas ekraanipildid pooleks. Kõige inetuma vea parandus oli üks rida CSS-i.
GeekBye nupus "ekspordi see koosolek PDF-ina" peidab end rahuldust pakkuv trikk: selle taga pole ühtegi PDF-teeki. Ei jsPDF-i, ei pdfmake'i, ei pdfkit-i, ei peata puppeteeri. Kogu funktsioon on peidetud brauser. Äpp ehitab HTML-lehe, mis kirjeldab koosolekut — transkriptsioonikirjed, ekraanipildid, ajatemplid — laadib selle nähtamatusse Electroni aknasse ja palub sellel aknal end PDF-iks printida. webContents.printToPDF teeb ülejäänu. See üksainus arhitektuurivalik on kogu selle väljalaske lugu, sest see tegi funktsiooni odavaks ehitada ja andis sellele iga vea, mis päris brauseril on.
Enne on sama väljalase
Tavaliselt algavad need postitused sõnadega "vaata, kuidas see varem töötas." Seekord pole mingit varem. v1.8.9 muudatuste logi loeb nagu hooldus — "fixed page breaks splitting screenshots and filtered empty entries" — nagu oleks PDF-eksport olemasolev funktsioon, mida lapitakse. See polnud. Esimene commit, mis selle tutvustab, feat: add PDF export via Electron printToPDF, saabub kolm päeva samasse väljalasketsüklisse, ja iga "parandus" muudatuste logis on sama nädala iteratsioon koodil, mis oli tunde vana. Funktsioon sündis, purunes ja karastati — kõik ühe sildi sees. Seda tasub valjusti öelda, sest see on paljude tarnimiste aus kuju: viimistletud punkt väljalaskemärkmetes ja asja sassis sünd on sageli üks ja sama väljalase.
Miks üldse peidetud brauser
Mehhanism tasub mõista enne vigu, sest kõik vead põlvnevad sellest. Kõik elab ühes peaprotsessi failis, electron/ipcHandlers/pdfExportHandler.ts. Kui renderdaja käivitab 'pdf:export-report' IPC-i, siis käitleja:
- Kutsub
buildReportHtml(), et koosoleku ajajoonest kokku panna täielik HTML-string — üks.itemplokk transkriptsioonikirje või ekraanipildi kohta. - Kirjutab selle HTML-i ajutisse faili kaustas
os.tmpdir(). - Laadib faili peidetud aknasse —
new BrowserWindow({ show: false, opacity: 0, focusable: false, skipTaskbar: true }). - Ootab, kuni pildid laadivad, siis kutsub
win.webContents.printToPDF({ printBackground: true, margins: { … } }). - Salvestab tulemuse kausta
app.getPath('downloads')kuiMeeting-Report-<timestamp>.pdf.
Veetlus on ilmne. PDF-teek paneb sind kõike käsitsi paigutama — joonista tekst kohta (x, y), mõõda see, liiguta kursorit, halda leheküljemurde ise. Brauser teeb seda kõike juba: sa kirjutad <div>-e ja CSS-i ning Chromiumi paigutusmootor lehekülgedeks jaotab sinu eest. Sa ei õpi joonistus-API-t; sa kirjutad veebilehte. Dokumendi jaoks, mis on enamjaolt stiliseeritud tekst ja põimitud pildid, on see hiiglaslik otsetee.
Konks on selles, et sa pärisid ka brauseri kõigi selle servajuhtumitega. Kolm neist ilmusid esimese nelja päeva jooksul.
Valge välgatus. Peidetud aken macOS-il pole usaldusväärselt peidetud. Esimesed katsed paigutasid selle ekraanilt välja (x: screenWidth + 1000), ja see välgatas ikka enne printimist üheks kaadriks valge ristküliku ekraanile. Parandus, pärast kolme katset, oli lakata toetumast positsioonile või show: false-le ja seada opacity: 0 — täiesti läbipaistev aken ei välgata isegi siis, kui kompositor selle korraks kuvab. Commit ütleb seda otse: "show:false with off-screen positioning still flashes on macOS."
URL-i pikkuse piirang. Esimene versioon põimis kogu HTML-i — ekraanipildid ja kõik — data: URL-ina. See töötab, kuni koosolekus on paar base64-kodeeritud ekraanipilti, mispeale URL purjetab Chromiumi ~2 MB data-URL-i piirangust mööda ja laadimine ebaõnnestub otse veaga ERR_INVALID_URL. Sellepärast kirjutab ülalolev samm 2 ajutise faili ja teeb loadFile() selle asemel, et laadida data URL: failiteel pole pikkuse piirangut. (See ~2 MB on selle terve loo üks number, ja see on reasisene hinnang koodikommentaaris, mitte miski, mida keegi benchmarkis — käsitle seda kui "piisavalt suur, et vastu põrgata," mitte täpset läve.)
Varjestamata tekst. Transkriptsiooni tekst on suvaline kasutajasisu, ja see visati otse HTML-stringi. Iga transkriptsioon, mis juhtus sisaldama midagi nagu <div> või <script>, murraks lehe või hullemat. Nii kasvas käitlejale escapeHtml() läbimine üle iga stringi, mida see interpoleerib. Hetkest, mil su PDF on veebileht, on HTML-i süstimine ka sinu probleem.
Tühjad kastid, mis polnud tühjad
Nüüd peamine viga, sest see on parim. Kasutajad teatasid eksporditud PDF-idest tühjade kastidega — kirjed, mis näitasid ajatemplit ja märki, aga mitte ühtegi ekraanipilti, ainult tühja ruumi seal, kus pilt peaks olema. Ilmne lugemine on "eksport viskab pildiandmed minema," ja ilmne parandus on "filtreeri tühjad kirjed välja." Meeskond lisas selle filtri — sellest hetke pärast rohkem — aga see polnud tegelik põhjus.
Tegelik põhjus oli lehekülgedeks jaotamine. Iga kirje on .item konteiner, mis hoiab ajatemplit, märki ja pilti. Chromium, PDF-i printides, oli rõõmuga nõus murdma lehte kirje keskel — renderdades ajatempli ja märgi ühe lehe alaossa ja lükates pildi järgmisele. See, mis nägi välja nagu tühi kast, oli kirje ülemine pool, mille pilt oli järgmisele leheküljele üle voolanud. Andmed olid kõik olemas; paigutus oli need giljotineerinud. Commit on väärdiagnoosi suhtes avameelne: "The empty boxes in exported PDFs were caused by Chromium's printToPDF splitting .item containers across pages — the time/badge rendered on one page while the image overflowed to the next."
Parandus on üks rida CSS-i:
.item {
break-inside: avoid;
}
break-inside: avoid ütleb paigutusmootorile, et hoia kirje tervikuna — kui see ei mahu lehele jäänud ruumi, liiguta kogu kirje järgmisele leheküljele, selle asemel et seda poolitada. See on peidetud brauseri arhitektuuri tasu miniatuuris öelduna: funktsiooni inetuim, kõige rohkem raporteeritud viga parandati mitte leheküljekõrguse aritmeetika ja käsitsi addPage() kutsetega, vaid ühe CSS-omadusega, mida brauser juba oskas austada. Sa ei kirjutanud lehekülgedeks jaotajat; sa palusid sellel, mille pärisid, käituda.
Kaks kihti tegelikult-tühja filtreerimist
Seal oli ka tõeliselt tühje kirjeid — lihtsalt mitte need, mis kaste põhjustasid. Tühjad duplikaadid kiirest topelt-Cmd+Enter-ist, ekraanipildid ilma eelvaateta, transkriptsioonikirjed, mis olid ainult tühikud. Need filtreeriti, ja huvitaval kombel filtreeriti neid kaks korda, kahes kihis:
- Käitlejas valvab
buildReportHtml()iga kirjet —if (!item.content?.trim()) return ''teksti jaoks, ja piltide jaoksconst safeSrc = item.preview?.startsWith('data:image/') ? item.preview : ''; if (!safeSrc) return ''— siis.filter(Boolean)viskab tühjad minema. See viimane kontroll teeb topelttööd: see on kehtivusvärav ja väike turvakontroll, et lehele jõuaks ainult päris pildiandmed. - Ajajoone allikas, renderdajas, filtreeritakse sama tühjus enne, kui kirjet üldse lisatakse —
if (!entry.text?.trim()) return,if (!screenshot.preview) return.
Ajaloo tore detail on meetod. Esimene commit lisas logger.debug('PDF timeline item', …) tsükli sõnaselgelt "for debugging empty entries." Kui see logimine paljastas, kust tühjad tegelikult tulid — sealhulgas eraldi ekraanipiltide deduplikatsiooni viga, kus kiired jäädvustused tootsid tühje duplikaate — parandas järgnev commit algpõhjuse ülesvoolu ja eemaldas silumislogimise. Lisa instrumentatsioon, et põhjus leida, paranda põhjus, kustuta instrumentatsioon. Muudatuste logi punkt "filtered empty entries" sisaldab vaikselt kogu seda lisa-siis-eemalda kaart.
v1.8.12: "excludes AI responses," loe diff-i
Kolm väljalaset hiljem veel üks muudatus — ja see on hea õppetund koodi lugemises muudatuste logi asemel. v1.8.12 märkus ütleb, et PDF-eksport nüüd "excludes AI responses for cleaner meeting records." Tõsi, aga poolik.
Ekspordi ajajoon märgistab iga kirje type-ga: 'transcript' | 'chat_user' | 'chat_assistant' | 'screenshot'. Räägitud koosolekurida on transcript; küsimus, mille sa assistendipaneeli trükkisid, on chat_user; AI vastus on chat_assistant. v1.8.12 muudatus on üheksarealine redigeerimine ühes renderdaja failis, ja määrav rida on filter, mis on paigutatud vahetult enne ekspordi koorma ehitamist:
timeline.filter((item) => item.type === 'transcript' || item.type === 'screenshot')
See on lubatute nimekiri, mitte keelatute nimekiri. See ei eemalda chat_assistant kirjeid; see hoiab ainult transcript-i ja screenshot-i ning viskab kõik muu minema — mis tähendab, et see eemaldab kasutaja enda chat_user küsimused koos AI vastustega. Commiti keha on täpsem kui muudatuste logi: "PDF export keeps only transcripts and screenshots, no chat messages." Eksporditud kirje on nüüd puhtalt koosolek — mis öeldi ja mis oli ekraanil — kogu assistendi külgpaneeli vestlus sellest eemaldatud.
Ja see on tootemõistes õige otsus. GeekBye vestlus on privaatne assistent, keda konsulteerid kõne ajal — küsi sellelt ekraanipildi kohta, saa kiire selgitus. Need vahetused on su mustandiplokk, mitte osa koosolekust. Koosolekukirje, mida kellegagi jagaksid, peaks sisaldama koosolekut, mitte su kõrvalvestlusi AI-ga selle üle. Lubatute nimekiri lihtsalt teeb "koosolekust" selle sõnasõnalise definitsiooni, mis eksporditakse.
Kolm asja, mida see väljalase meile õpetas
- Brauser on PDF-renderdaja, mis sul juba on. Kui su dokument on struktureeritud tekst ja pildid, annab
printToPDFüle peidetudBrowserWindow-i sulle päris lehekülgedeks jaotamise, päris fontide renderdamise ja CSS-paigutuse tasuta — mitte mingit PDF-i joonistus-API-t. Sa vahetad teegisõltuvuse brauseri servajuhtumite vastu, mis on sageli parem vahetus. - Viga, mida sa näed, ja viga, mis eksisteerib, pole alati sama. "Tühjad kastid" karjusid puuduvad andmed; põhjus oli poolitatud konteiner. Instrumenteeri enne, kui eeldad — meeskonna silumislogimine on see, mis muutis usutava-aga-vale paranduse (filtreeri tühjad) tegelikuks (
break-inside: avoid). - Muudatuste logi on kokkuvõte; filter on tõde. "Excludes AI responses" on lubatute nimekiri, mis viskab minema ka su enda vestlusküsimused. Kui väljalaskemärkus kirjeldab käitumismuutust, ütleb diff sulle selle täpsed servad — ja siin on täpne serv "ellu jääb ainult koosolek."
Eelmise peatüki jaoks v1 loos — OCR-ist piksliteni ilma varuvarianti kaotamata (v1.8.6–v1.8.7); ja kogu kaare jaoks tarkvara täiuslikkuseni tarnimise anatoomia.