Codice e controllo

Nessuna scatola nera. Il sorgente è lì, e si apre.

Costruire parlando non vuol dire non poter guardare. Ogni file del tuo progetto si legge, si corregge e si scarica — da un editor vero dentro il sito — e ogni pezzo ha anche il suo pannello ordinato, per quando la GUI è più veloce del codice.

Come si accende

La modalità avanzata, e da lì c'è tutto

Un interruttore nella sezione Avanzate del progetto: aggiunge la sezione Codice e, in ogni schermata, dice da quale file nasce quello che stai guardando. È spenta di partenza — chi costruisce parlando non ha bisogno di vederla — e la accendi tu, per te.

studio.spelltech.cloud
La sezione Codice dello Studio: il workbench in stile VS Code con l'editor a sinistra, l'albero dei file sul bordo destro, un paio di schede aperte e il selettore di zona (Tutto | server/ | client/ | agents/ | repos/ | tests/ | Altro) nella barra in cima.
Ingrandisci
La sezione Codice dello Studio: il workbench in stile VS Code con l'editor a sinistra, l'albero dei file sul bordo destro, un paio di schede aperte e il selettore di zona (Tutto | server/ | client/ | agents/ | repos/ | tests/ | Altro) nella barra in cima.

Più file aperti insieme

Schede che si trascinano e si affiancano, come in un editor da scrivania. Non un campo di testo con dentro un file per volta.

L'albero dei file

Crea, rinomina, sposta, seleziona a gruppi, copia e incolla, carica trascinando dal desktop, scarica un file o una cartella in zip.

Cerca e sostituisci, su tutto

Per percorso e dentro il contenuto, con maiuscole, parola intera ed espressioni regolari. Il risultato si apre dov'è.

Ti dice cosa hai sbagliato

È l'editor vero di Visual Studio Code: completamento, tipi, errori sottolineati mentre scrivi.

Diviso per zone

Tutto il progetto, o solo server/, client/, agents/, repos/, tests/: si sceglie quale albero montare, invece di cercare in una cartella sola.

Le versioni pubblicate in sola lettura

Un menu sceglie la copia da aprire: l'ambiente di prova, che si scrive, o una versione pubblicata, che non cambia più e si legge soltanto. Si scrive sull'ambiente di prova, e si pubblica.

Il sorgente

Com'è fatto un progetto, davvero

Sei file di un progetto vero, e accanto a ognuno la cosa che conta di più: quello che non hai dovuto scrivere. Non è che il codice sia poco — è che dice solo la parte che è tua.

data/tables/pratiche.json
{
  "description": "Una richiesta aperta da una persona e lavorata dallo staff.",
  "scope": "space",
  "columns": {
    "clienteId":   "ref:clienti?",
    "titolo":      { "type": "text", "minLen": 3, "maxLen": 160 },
    "descrizione": { "type": "text?", "maxLen": 4000 },
    "stato":       "enum:aperta|in_corso|sospesa|chiusa=aperta",
    "priorita":    "enum:bassa|media|alta=media",
    "scadenza":    "datetime?"
  },
  "indexes": ["stato", "createdAt"],

  "flow": {
    "column": "stato",
    "paths": {
      "aperta":   ["in_corso", "chiusa"],
      "in_corso": ["sospesa", "chiusa"],
      "sospesa":  ["in_corso", "chiusa"],
      "chiusa":   []
    },
    "error":   "BAD_TRANSITION",
    "message": "Una pratica chiusa non si riapre"
  },

  "access": {
    "read":   { "rows": "own", "all": ["staff"] },
    "create": true,
    "update": true,
    "delete": true
  }
}
Quello che non hai scritto

Da qui nascono la tabella vera sul database, le operazioni di lettura e scrittura, la validazione dei campi, la paginazione, l'ordinamento — e il filtro che fa vedere a chi apre una pratica soltanto le proprie, mentre lo staff le vede tutte (`all`). Chi può leggere lo dice il permesso `pratiche.read` nei ruoli; quali righe, questo file. E il `flow` fa rispettare al database a ogni scrittura che una pratica chiusa non torni aperta. Nessuna di quelle cose è scritta da qualche parte: è dedotta da questo file. Nemmeno `id`, `createdAt`, `updatedAt` e le colonne che dicono a chi appartiene la riga sono qui: le mette la macchina.

server/pratiche/apri.ts
import { action, db, fail } from "@spelltech/api-sdk";

export default action({
  description: "Apre una nuova pratica a nome di chi chiama",
  input: {
    titolo:      { type: "text", minLen: 3, maxLen: 160 },
    descrizione: { type: "text?", maxLen: 4000 },
    priorita:    "enum:bassa|media|alta?",
    clienteId:   "ref:clienti?",
  },
  toast: "Richiesta inviata: la trovi fra le tue pratiche",

  async run(data) {
    if (data.clienteId) {
      const cliente = await db.clienti.findFirst({ where: { id: data.clienteId } });
      if (!cliente) fail("CUSTOMER_NOT_FOUND", "Questo cliente non è in anagrafica", { fields: { clienteId: "Scegline uno dall'elenco" } });
    }

    const pratica = await db.pratiche.create({
      data: {
        titolo: data.titolo.trim(),
        descrizione: data.descrizione,
        priorita: data.priorita ?? "media",
        clienteId: data.clienteId,
      },
    });

    return { id: pratica.id };
  },
});
Quello che non hai scritto

Un file in una cartella e l'operazione esiste, col nome del percorso: `pratiche.apri`, che è anche il nome del suo permesso — chi lo tiene nel suo ruolo la chiama, gli altri no. Niente rotta da registrare, niente controller, niente wiring. I campi in ingresso si dichiarano e arrivano già validati, il database è quello del progetto e di nessun altro, e il messaggio a schermo lo chiede l'azione al frontend. La chiave `trigger`, qui assente, è quella che fa partire lo stesso tipo di file a un orario o alla scrittura di una riga.

client/app/pratiche/index.tsx
import { useState } from "react";
import { definePage, useApiState } from "@spelltech/react";
import { Badge, Card, PageHeader, ToggleGroup, ToggleGroupItem } from "@spelltech/ui";

export default definePage(Pratiche, {
  title: "Pratiche",
  nav: { label: "Pratiche", icon: "folder-open", order: 20 },
});

const WHERE = {
  aperte: { stato: { in: ["aperta", "in_corso", "sospesa"] } },
  chiuse: { stato: "chiusa" },
  tutte:  {},
};

function Pratiche() {
  const [filtro, setFiltro] = useState("aperte");
  const { data } = useApiState("pratiche.list", {
    where: WHERE[filtro], orderBy: { createdAt: "desc" }, take: 200,
  });

  return (
    <>
      <PageHeader title="Pratiche" description="Tutto quello che è stato chiesto, con il suo stato." />

      <ToggleGroup type="single" value={filtro} onValueChange={(v) => v && setFiltro(v)}>
        <ToggleGroupItem value="aperte">Aperte</ToggleGroupItem>
        <ToggleGroupItem value="chiuse">Chiuse</ToggleGroupItem>
        <ToggleGroupItem value="tutte">Tutte</ToggleGroupItem>
      </ToggleGroup>

      {data?.items.map((p) => (
        <Card key={p.id} className="p-4">
          <div className="font-medium">{p.titolo}</div>
          <Badge>{p.stato}</Badge>
        </Card>
      ))}
    </>
  );
}
Quello che non hai scritto

Il file è la rotta e la voce di menu. Non c'è un colore scritto: i componenti leggono le variabili CSS del progetto. E `useApiState` non è una fetch — tiene il dato in cache, lo rilegge quando il server dice che è cambiato e aggiorna la lista sotto gli occhi di chi la sta guardando.

server/prenotazioni/promemoria.ts
import { action, db, mail, users } from "@spelltech/api-sdk";

export default action({
  description: "Manda il promemoria il giorno prima",
  trigger: { cron: { for: "prenotazioni", at: "promemoriaAt" } },

  async run(prenotazione) {
    if (prenotazione.stato !== "confermata" || !prenotazione.ownerUserId) return;

    const persona = await users.get(prenotazione.ownerUserId);
    if (!persona?.email) return;

    const fascia = await db.fasce.findFirst({ where: { id: prenotazione.fasciaId } });
    await mail.send({
      to: persona.email,
      template: "promemoria",
      data: { nome: prenotazione.nome, quando: fascia?.inizio ?? "" },
    });
  },
});
Quello che non hai scritto

L'orario sta nel file, non in una schermata di configurazione da tenere allineata: qui è il campo `promemoriaAt` di ogni prenotazione, e la piattaforma sveglia il file una volta per riga quando quell'istante arriva. Gira una volta sola anche con più copie del runtime in piedi, e le email partono col tuo aspetto e dal tuo mittente perché il progetto ha già un tema per le email. Una scrittura, un'email, un pagamento, un webhook, una coda: sono la stessa chiave `trigger`. Una coda dichiara anche quanti tentativi fare (`maxAttempts`) e con che attesa fra l'uno e l'altro (`backoff`); un errore dell'infrastruttura rimette il lavoro in coda senza consumarne uno.

project.json
{
  "timezone": "Europe/Rome",
  "locale": "it",

  "roles": {
    "owner": { "label": "Titolare", "can": ["*"] },
    "staff": {
      "label": "Staff",
      "description": "Lavora le pratiche di tutti e tiene l'anagrafica.",
      "can": ["pratiche.*", "clienti.*", "note.*"]
    },
    "cliente": {
      "label": "Cliente",
      "description": "Apre le proprie richieste e ne segue l'avanzamento.",
      "default": true,
      "can": ["pratiche.read", "pratiche.apri", "note.read", "note.aggiungi"]
    }
  }
}
Quello che non hai scritto

Un permesso non si dichiara: `pratiche.apri` esiste perché esiste l'azione che porta quel nome, `pratiche.read` perché la tabella ha la lettura, quindi non c'è un elenco da tenere allineato né un nome che si possa scrivere storto. Qui si scrive una volta sola chi lo tiene, e vale in ogni schermata, in ogni azione e in ogni chiamata con una chiave API — niente `if` sul ruolo sparso per il codice. Il permesso dice SE; quali RIGHE, lo dice la tabella.

machines/render/machine.json
{
  "region": "fra",
  "size":   { "cpuKind": "shared", "cpus": 2, "memoryMb": 2048 },
  "diskGb": 10,

  "packages": { "base": "debian:bookworm-slim", "apt": ["ffmpeg", "nodejs"] },
  "cmd":      ["node", "server.mjs"],

  "ports": [
    { "port": 8080, "protocol": "http", "access": "public" }
  ],

  "secrets": ["STRIPE_KEY"],
  "sleep":   { "enabled": true, "afterMin": 3 },
  "roles":   ["render"],

  "envs": { "prod": { "size": { "cpus": 4, "memoryMb": 4096 }, "diskGb": 20 } }
}
Quello che non hai scritto

Una macchina è una cartella del progetto: il manifesto e accanto il codice che ci gira dentro. Le porte dichiarate sono anche il firewall — quello che non è scritto qui non si raggiunge — e la macchina si addormenta da sé quando nessuno le parla, quindi il silenzio non si paga. Ce n'è una per ambiente: `envs` dice cosa cambia in produzione (taglia, disco, sonno), il resto è uguale ovunque.

Le stanze

Un pannello ordinato per ogni pezzo

Un editor è la risposta giusta a metà delle domande. Per l'altra metà — guardare mille righe, capire chi può chiamare un'azione, vedere quanto occupano i file — serve una schermata fatta per quello, e c'è.

Database

Le tabelle e le viste in schede, come in un client vero: griglia paginata e ordinabile, modifica di un valore col doppio click, aggiunta e cancellazione di righe, e una scheda per le query SQL scritte a mano.

studio.spelltech.cloud
Il Database dello Studio con una tabella aperta nella griglia a card, la barra in cima e la sidebar con l'elenco di tabelle e viste.
Ingrandisci
Il Database dello Studio con una tabella aperta nella griglia a card, la barra in cima e la sidebar con l'elenco di tabelle e viste.

Il disegno delle relazioni

Il diagramma di come le tabelle si legano, disposto da sé: tabelle in blu, viste in verde, le chiavi esterne come archi. Si trascina, si ingrandisce, e il doppio click su una tabella la apre nei dati.

studio.spelltech.cloud
Il diagramma ER del Database: nodi delle tabelle e delle viste con gli archi delle relazioni, e i controlli galleggianti in basso a sinistra.
Ingrandisci
Il diagramma ER del Database: nodi delle tabelle e delle viste con gli archi delle relazioni, e i controlli galleggianti in basso a sinistra.

API

L'elenco di tutte le unità del progetto, ognuna con ciò che la fa partire — una chiamata, un orario, una tabella, un'email, un pagamento, un webhook, una coda, un canale. Di una chiamata vedi la riga pronta da copiare, i campi che accetta e chi può farla; di tutte le altre il tasto «Esegui adesso» e le ultime esecuzioni. In schede: i test, e i canali con chi c'è dentro ora.

studio.spelltech.cloud
La sezione API dello Studio: l'elenco delle unità con il badge di cosa le fa partire e il pannello di dettaglio di un'azione aperto a destra.
Ingrandisci
La sezione API dello Studio: l'elenco delle unità con il badge di cosa le fa partire e il pannello di dettaglio di un'azione aperto a destra.

Documenti

Due specie in due schede: i file pubblici, serviti a chiunque dal tuo dominio, e quelli privati, a cui si arriva solo passando dall'applicazione. Con quanto occupano, quanto pesano davvero dopo la deduplicazione, e chi li nomina nel codice.

studio.spelltech.cloud
La sezione Documenti dello Studio: in cima la panoramica d'uso — quanti documenti, quanto pesano, la distribuzione per tipo e i più pesanti — e sotto il browser a cartelle, con lo scambio fra pubblici e privati e fra ambiente di lavoro e produzione.
Ingrandisci
La sezione Documenti dello Studio: in cima la panoramica d'uso — quanti documenti, quanto pesano, la distribuzione per tipo e i più pesanti — e sotto il browser a cartelle, con lo scambio fra pubblici e privati e fra ambiente di lavoro e produzione.

Segreti e chiavi

I segreti che il codice legge, nella cassaforte del progetto: prova e produzione affiancati, valori nascosti e da rivelare uno alla volta dopo una riconferma d'identità. Accanto, i valori non segreti che una pagina legge. E le chiavi API con cui un altro sistema legge e scrive i tuoi dati: ruoli, limiti al secondo, scadenza, revoca.

studio.spelltech.cloud
La sezione Segreti dello Studio con i valori mascherati, e accanto la sezione API key con una chiave appena generata.
Ingrandisci
La sezione Segreti dello Studio con i valori mascherati, e accanto la sezione API key con una chiave appena generata.

Email

Le email del progetto con l'anteprima resa esattamente come partirà, l'editor del corpo accanto, le variabili rilevate coi valori di prova, e l'invio a un indirizzo per vederla arrivare davvero.

studio.spelltech.cloud
La sezione Email dello Studio: l'anteprima di un'email a sinistra e l'editor del corpo, col pannello delle variabili a destra.
Ingrandisci
La sezione Email dello Studio: l'anteprima di un'email a sinistra e l'editor del corpo, col pannello delle variabili a destra.
Il criterio

Quando si scrive codice, e quando no

La regola dello stack è una sola: quello che si può dichiarare si dichiara, quello che è logica vera si scrive. Serve a te come serve all'agente — meno modi di sbagliare, e le cose giuste sono anche le più brevi.

Il progetto
Il sorgentework/
Dove vive ogni cosa
Le tabelle, dichiaratedata/
La logica che è tuaserver/
Pagine e aspettoclient/
I ruoli di partenzaproject.json
Cosa ci pensa la piattaforma
Operazioni e validazionenascono dalle tabelle
Permessi e tempo realeosservati, non scritti
  1. Il progetto
    Il sorgentework/
  2. Dove vive ogni cosa
    Le tabelle, dichiaratedata/
    La logica che è tuaserver/
    Pagine e aspettoclient/
    I ruoli di partenzaproject.json
  3. Cosa ci pensa la piattaforma
    Operazioni e validazionenascono dalle tabelle
    Permessi e tempo realeosservati, non scritti
Fili viola: quello che scrivi o dichiari · fili ambra: quello che fa la piattaforma

Non lo scrivi per le cose che si dichiarano

Una tabella è un file di dati e da lì nascono le operazioni, la validazione, la paginazione e il filtro per proprietario. Anche le regole che di solito si scrivono a mano si dichiarano sulla tabella: due prenotazioni che non si sovrappongono, un massimo di righe per persona, un conto a crediti e debiti, le fasce orarie, e le transizioni di stato ammesse — una pratica chiusa non torna aperta — e le fa rispettare il database a ogni scrittura. I ruoli sono dati, e i permessi sono i nomi delle operazioni. L'aspetto è un foglio di stile con le variabili CSS. Non c'è codice da scrivere per queste cose, quindi non c'è codice da mantenere.

Lo scrivi per la logica che è tua

Il calcolo del totale, la regola che decide se una prenotazione si può accettare, il conto del mese: quello è il tuo mestiere e si scrive in un file. TypeScript o JavaScript, come preferisci: i tipi non si compilano, si controllano — e i tipi del tuo database, delle tue azioni e delle tue rotte li scrive la piattaforma, così un nome sbagliato si vede prima di provarlo.

E lo eietti quando la parte generata non basta

Un'operazione generata si sostituisce con la tua — un file col nome dell'operazione, accanto alla tabella — una per volta, senza rinunciare alle altre. Un componente del kit si rimpiazza mettendo il tuo file nel progetto. Nessuna esportazione, nessun punto di non ritorno.

I controlli

Cosa si controlla prima di dire «fatto»

Il progetto attinge a un deposito di librerie condiviso, con una libreria sola per ogni lavoro — pdfmake per un PDF nuovo, exceljs per un foglio Excel, bwip-js per un codice a barre —: quello che serve c'è già installato, e la schermata Librerie dice cosa può entrare nel backend e cosa nelle pagine. Le chiavi dei servizi esterni non stanno nel codice: stanno nella cassaforte del progetto, e il codice le legge da lì.

La verifica finale

Quando l'agente finisce, fa girare la verifica finale: ogni scenario, i controlli derivati dalle dichiarazioni, il disegno di ogni pagina per ogni persona che la apre e i tipi del frontend e del backend, senza nessun modello. Un errore di tipo è un fallimento; un controllo che non ha potuto guardare niente è segnalato, non conta come verde. L'agente legge i fallimenti e li corregge prima di rispondere.

La copertura, scritta

Gli scenari sono dati, non codice: chi fa cosa e cosa deve risultare. La copertura dice cosa ne ha uno — per ogni azione il percorso che riesce e ogni rifiuto che può dare, per ogni pagina ogni persona che la apre, per ogni email la sua composizione — e cosa ancora no.

Il codice gira in una gabbia

Le tue unità girano isolate dal resto: senza privilegi di sistema, senza file né processi, con la rete aperta solo verso l'esterno e non verso i servizi interni della piattaforma. Le pagine che il server scrive in anticipo non vedono il database né le credenziali, e le loro richieste in uscita non raggiungono la rete interna.

L'agente prova davvero

Esegue l'azione come un utente vero, apre la pagina in un browser e percorre il flusso, e i controlli che contano si salvano come scenari del progetto, da rieseguire su un ambiente di test che si disfa alla fine. Un errore trovato subito costa un messaggio, uno trovato dopo costa una sera.

I documenti restano di chi li ha

Una riga può nominare un documento privato solo se chi la scrive l'ha appena caricato o lo vede già in una riga sua. Nessuno fa puntare un proprio record al file di un altro per scaricarlo: la regola sta nel punto da cui passano tutte le scritture, generate o tue.

Ogni modifica ha un ritorno

Si torna al punto di prima, e le copie programmate riportano l'ambiente com'era — anche solo il pezzo che si è rotto.

Se un giorno vai via

Quello che ti porti

Un lock-in non si smentisce a parole: si smentisce dicendo cosa esce. Ecco cosa esce.

Il codice si scarica

Un file, una cartella o l'albero in zip, dalla stessa schermata in cui lo modifichi.

I dati sono tuoi

Il database del progetto è tuo e separato da tutti gli altri; le righe si leggono, si interrogano in SQL e si portano via.

E può stare sul tuo GitHub

La piattaforma collega il GitHub della tua organizzazione e mette lì il codice nativo: il repository è tuo, con la tua cronologia.

Quello che non si porta via è la piattaforma: le operazioni generate, i permessi osservati, il tempo reale e la pubblicazione sono cose che fa lei. È il motivo per cui il tuo codice è così corto.

Si parte gratis

Il modo più veloce di controllarlo è aprirlo.

Fai un progetto, accendi la modalità avanzata e leggi tutto quello che l'agente ha scritto. La dotazione iniziale basta.