Ai tempi di Yoox, il nostro team viveva in due dimensioni parallele. La board digitale su Jira tracciava ogni movimento, ogni transizione di stato, ogni commento. Ma era la board fisica, quella parete di post-it colorati nell’open space, a dare forma al nostro lavoro. Bastava alzare lo sguardo per capire a che punto eravamo, cosa mancava, chi aveva bisogno di aiuto. Non c’era bisogno di aprire browser, navigare tra tab, filtrare viste. Il lavoro era semplicemente lì, tangibile, presente nello stesso spazio fisico che condividevamo con i nostri corpi.
Task che fluiscono attraverso le swim lanes - il ritmo visivo del lavoro in movimento
Quando il lavoro remoto è diventato la norma, quella seconda dimensione è scomparsa. Non c’è più una parete condivisa, non ci sono più post-it da spostare durante gli stand-up. Tutto vive sugli schermi, in quella sottile membrana di pixel che separa i nostri corpi dal lavoro che facciamo. E qualcosa, silenziosamente, si perde.
PhabPrint è nato da questa riflessione: la fisicità non è nostalgia, è presenza mentale. Un task che esiste solo in forma digitale può essere minimizzato, nascosto, dimenticato. Un pezzo di carta sulla scrivania occupa spazio reale, crea peso, richiede attenzione. Non compete con notifiche o badge rossi. Semplicemente esiste, paziente, accanto alla tazza di caffè e alla tastiera.
Per chi lavora in full remote, ricreare quella dimensione tangibile diventa ancora più importante. Non ci sono colleghi che passano dalla scrivania, non ci sono stand-up davanti a una parete di post-it. La giornata rischia di diventare una sequenza indifferenziata di videocall e notifiche Slack. Stampare un task, appenderlo su una board personale, spostarlo fisicamente quando cambia stato – sono azioni che riportano il corpo nel loop del lavoro, non solo la mente. E il corpo ricorda meglio degli occhi.
Il Flusso delle Informazioni
L’architettura di PhabPrint è volutamente lineare. Nessuna coda distribuita, nessun microservizio, nessuna complessità che nasconde semplicità. Solo un flusso chiaro di informazioni che attraversa sottili strati di trasformazione:
┌──────────────────────┐
│ Phabricator │
│ Conduit API │
│ maniphest.search │
└──────────┬───────────┘
│
│ HTTP Polling
│ (ogni 15 minuti)
│
▼
┌──────────────────────┐
│ PhabPrint Service │
│ Node.js │
└──────────┬───────────┘
│
│ Filtro task:
│ • Colonne sprint
│ • Assegnatario (il tuo PHID)
│ • Stato (open)
│ • Cache (evita duplicati)
│
▼
┌──────────────────────┐
│ ESC/POS Formatter │
│ Layout scontrino │
└──────────┬───────────┘
│
│ Comandi di stampa
│ (font, allineamento,
│ taglio carta)
│
▼
┌──────────────────────┐
│ Stampante Termica │
│ USB/Network │
└──────────┬───────────┘
│
▼
📄 Ticket
Ogni task che entra in una colonna configurata come “sprint” viene automaticamente trasformato in uno scontrino fisico. Il sistema tiene traccia di cosa ha già stampato per evitare duplicati, ma permette di resettare questa cache quando inizia un nuovo sprint.
L’Anatomia del Codice
Il cuore del sistema vive in poche centinaia di righe di JavaScript. La prima sfida è comunicare con Phabricator. La nostra istanza gira sull’infrastruttura Wikimedia, che usa TLS fingerprinting per bloccare client HTTP standard come axios o fetch. La soluzione è usare curl direttamente:
async
conduitCall(method, params = {})
{
const url = `${this.baseUrl}/${method}`;
const curlArgs = ['--data-urlencode', `api.token=${this.apiToken}`];
// Phabricator usa encoding form in stile PHP per parametri annidati
const flatParams = this.flattenParams(params);
for (const [key, value] of Object.entries(flatParams)) {
curlArgs.push('--data-urlencode', `${key}=${value}`);
}
const curlCmd = `curl -s -X POST ${curlArgs.map(a => `'${a}'`).join(' ')} '${url}'`;
const output = execSync(curlCmd, {encoding: 'utf-8'});
const data = JSON.parse(output);
if (data.error_code) {
throw new Error(`Errore API Phabricator: ${data.error_info}`);
}
return data.result;
}
La funzione flattenParams trasforma oggetti JavaScript annidati nel formato che Phabricator si aspetta. Per esempio,
{ constraints: { assigned: ['PHID-USER-123'] } } diventa constraints[assigned][0]=PHID-USER-123.
Il filtraggio dei task sfrutta gli attachment dell’API, che permettono di recuperare non solo i dati base ma anche le relazioni con progetti e colonne:
async
getSprintTasks()
{
const result = await this.conduitCall('maniphest.search', {
constraints: {
assigned: [this.userPhid],
statuses: ['open'],
},
attachments: {
projects: true,
columns: true,
},
limit: 100,
});
// Filtra solo i task in colonne che contengono keyword sprint
const sprintKeywords = ['sprint', 'to do', 'in progress', 'doing'];
return result.data.filter(task => {
const columnsData = task.attachments?.columns?.boards;
if (!columnsData) return false;
return Object.values(columnsData).some(board =>
board.columns.some(col =>
sprintKeywords.some(keyword =>
col.name.toLowerCase().includes(keyword)
)
)
);
});
}
Questo approccio permette flessibilità: ogni team può configurare i propri nomi di colonne senza modificare il codice,
semplicemente aggiornando la variabile d’ambiente SPRINT_COLUMNS.
La parte di stampa usa la libreria escpos, che astrae i comandi ESC/POS (lo standard de facto per stampanti termiche).
Ogni scontrino segue un layout preciso:
async
printTask(task)
{
const device = new this.escpos.USB();
const printer = new this.escpos.Printer(device, {encoding: 'UTF-8'});
return new Promise((resolve, reject) => {
device.open(err => {
if (err) return reject(err);
const maxWidth = config.printer.paperWidth === 80 ? 48 : 32;
const separator = '─'.repeat(maxWidth);
printer
// Header: ID Task centrato e grande
.font('a')
.align('ct')
.size(2, 2)
.style('b')
.text(task.id)
.size(1, 1)
.style('normal')
.text(separator)
// Body: titolo e metadati
.align('lt')
.style('b')
.text(this.truncate(task.title, maxWidth))
.style('normal')
.text('')
.text(`Priorità: ${task.priority}`)
.text(`Punti: ${task.points}`)
.text(`Stato: ${task.status}`)
.text('')
.text(`Colonna: ${task.columns.join(', ')}`)
.text('')
// Footer: URL e taglio
.text(separator)
.align('ct')
.font('b')
.text(task.url)
.feed(4)
.cut()
.close();
this.markPrinted(task.id);
resolve(true);
});
});
}
Il sistema di cache è volutamente semplice: un file JSON che traccia gli ID dei task già stampati. Quando PhabPrint si
riavvia, carica questa cache per evitare di ristampare tutto. Un comando --clear-cache permette di resettare
all’inizio di ogni sprint.
La Filosofia del Tangibile
C’è qualcosa di quasi zen nel guardare un task materializzarsi. Il ronzio della stampante, lo srotolarsi della carta, il click finale quando lo scontrino si stacca. Non è solo feedback sensoriale, è un rituale che segna il confine tra digitale e fisico, tra astratto e concreto.
La stampante termica sa quando stai per rilassarti - e ha altri piani
Per chi lavora in remoto, questi rituali diventano ancora più importanti. Non ci sono colleghi che passano dalla scrivania, non ci sono stand-up davanti a una parete di post-it. La giornata rischia di diventare una sequenza indifferenziata di videocall e notifiche Slack. Stampare un task, appenderlo su una board personale, spostarlo fisicamente quando cambia stato – sono gesti che riportano il corpo nel loop del lavoro.
Gli scontrini stampati dalla stampante termica diventano i mattoncini per ricostruire quella board fisica che avevamo da Yoox. Una lavagna, un po’ di nastro colorato per creare le colonne, qualche calamita. Ogni mattina, lo stesso rituale: stampare i nuovi task, attaccarli nella colonna “To Do”, spostarli in “In Progress” quando inizi a lavorarci, farli scivolare in “Done” a fine giornata.
Una board Kanban fisica con swim lanes - la stessa sensazione che stiamo ricreando con i task stampati termicamente
È la stessa sensazione di quella parete nell’open space, ma nella solitudine del lavoro remoto. La differenza è che ora quella board la costruisci tu, nei tempi e nei modi che funzionano per te. Non è una replica nostalgica dell’ufficio, è uno spazio personale che riporta quel senso di progresso visivo e tangibile che gli schermi non possono dare.
E poi c’è il barattolo. O il contenitore, o la scatola, chiamatelo come volete. Quando un task è completato, lo stacchi dalla board, lo accartoccii e lo lasci cadere dentro. Un gesto semplice, quasi infantilmente soddisfacente. Ma alla fine dell’anno, quando svuoti quel contenitore, ti trovi davanti una massa fisica di lavoro completato. Non sono numeri su una dashboard, non sono burndown chart, non sono story point completati. Sono oggetti reali che occupano volume, che hanno peso. Puoi vedere quanto hai fatto, non in termini astratti ma in termini di spazio riempito. È una sensazione strana, quasi commovente, rendersi conto di quante cose passano per le tue mani in un anno, quanti problemi risolti, quante feature consegnate, quante review fatte. Il digitale non ti dà questo. Il digitale comprime tutto in bit, in notifiche archiviate, in ticket con stato “Risolto”. Ma quegli scontrini accartocciati nel barattolo, loro parlano di tempo vissuto, di lavoro fatto, di presenza.
Non è nostalgia per l’ufficio. È il riconoscimento che siamo esseri incarnati, che il nostro pensiero non vive solo nella testa ma anche nelle mani, negli occhi, nei movimenti che facciamo nello spazio. Un task digitale è pura astrazione. Uno scontrino sulla scrivania è un oggetto che esiste insieme ad altri oggetti, che crea relazioni spaziali, che può essere toccato, spostato, dimenticato sul tavolo e ritrovato dopo pranzo.
PhabPrint non risolve problemi di produttività o gestione progetti. Non ti farà chiudere più task o scrivere codice migliore. Ma forse, solo forse, renderà il tuo lavoro un po’ più presente, un po’ più reale, un po’ più tuo.
Il codice completo è disponibile su GitHub: https://github.com/alexghirelli/PhabPrint
La configurazione richiede solo un file .env con le tue credenziali Phabricator e una stampante termica USB da venti
euro. Il resto è silenzio, carta, e la quieta soddisfazione di vedere il lavoro materializzarsi.