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 nelle impostazioni 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 — ed è una proprietà del PROGETTO, non del tuo browser: un progetto può essere aperto di sotto mentre un altro resta un progetto con cui si parla.

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 (Frontend | API | Agents | Email | Other) 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 (Frontend | API | Agents | Email | Other) 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

Frontend, API, agenti, email, e il resto: si sceglie quale albero montare, invece di cercare in una cartella sola.

La produzione in sola lettura

Puoi guardare cosa è pubblicato, ma non scriverci: la pubblicazione successiva lo sovrascriverebbe. 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
{
  "columns": {
    "praticaId":       { "type": "id", "primaryKey": true },
    "ownerUserId":     { "type": "reference", "nullable": false },
    "clienteId":       { "type": "reference", "references": { "table": "clienti", "column": "clienteId" } },
    "titolo":          { "type": "text", "nullable": false, "minLen": 3, "maxLen": 160 },
    "stato":           { "type": "enum", "enum": ["aperta", "in_corso", "sospesa", "chiusa"], "default": "aperta" },
    "priorita":        { "type": "enum", "enum": ["bassa", "media", "alta"], "default": "media" },
    "scadenza":        { "type": "datetime", "nullable": true },
    "createdAt":       { "type": "datetime", "default": "now" }
  },
  "indexes": [{ "columns": ["ownerUserId"] }, { "columns": ["stato"] }],

  "access": {
    "ops": {
      "list":   { "who": ["owner", "role:staff"] },
      "get":    { "who": ["owner", "role:staff"] },
      "update": { "who": ["role:staff"] },
      "delete": { "who": ["role:staff"] }
    }
  }
}
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. Nessuna di quelle cose è scritta da qualche parte: è dedotta da questo file.

api/pratiche/apri.action
import { data, db, req, tokenData, fail } from "@spelltech/api-sdk";
import { PRIORITA } from "@lib/pratiche.js";

export const description = "Apre una nuova pratica a nome di chi chiama";
export const access = ["authenticated"];

export const input = {
  titolo:      { type: "text", required: true, minLen: 3, maxLen: 160 },
  descrizione: { type: "text", maxLen: 4000 },
  priorita:    "enum:bassa|media|alta?",
  clienteId:   "text?",
};

export default async () => {
  if (data.clienteId) {
    const cliente = await db.clienti.findFirst({ where: { clienteId: data.clienteId } });
    if (!cliente) fail("CLIENTE_INESISTENTE", "Questo cliente non è in anagrafica", { fields: ["clienteId"] });
  }

  const pratica = await db.pratiche.create({
    data: {
      ownerUserId: tokenData.userId,
      titolo: String(data.titolo).trim(),
      priorita: PRIORITA.includes(data.priorita) ? data.priorita : "media",
      stato: "aperta",
    },
  });

  req.toast({ type: "success", text: "Richiesta inviata: la trovi fra le tue pratiche" });
  return { data: { praticaId: pratica.praticaId } };
};
Quello che non hai scritto

Un file in una cartella e l'azione esiste: niente rotta da registrare, niente controller, niente wiring. L'accesso è una riga, 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.

frontend/apps/app/pratiche/index.tsx
import { useState } from "react";
import { definePage, useApiState } from "@spelltech/react";
import { Badge, Button, Card, EmptyState, 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, loading } = useApiState("pratiche.list", {
    data: { where: WHERE[filtro], sort: "createdAt", order: "desc", limit: 200 },
  });

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

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

      {data?.items.map((p) => (
        <Card key={p.praticaId} 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 il tema. 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.

api/promemoria.cron
import { db, mail } from "@spelltech/api-sdk";

export const schedule = "0 * * * *";   // ogni ora

const FINESTRA_ORE = 24;

export default async function () {
  const fino = new Date(Date.now() + FINESTRA_ORE * 3_600_000).toISOString();

  const fasce = await db.fasce.findMany({
    where: { inizio: { gte: new Date().toISOString(), lte: fino } },
    take: 500,
  });
  if (!fasce.length) return;

  const prenotazioni = await db.prenotazioni.findMany({
    where: { fasciaId: { in: fasce.map((f) => f.fasciaId) }, stato: "confermata" },
  });

  for (const p of prenotazioni) {
    if (p.promemoriaAt) continue;        // il timbro impedisce il doppio invio
    await mail.send({ to: p.email, template: "promemoria", data: { nome: p.nome } });
    await db.prenotazioni.update({
      where: { prenotazioneId: p.prenotazioneId },
      data: { promemoriaAt: new Date().toISOString() },
    });
  }
}
Quello che non hai scritto

L'orario sta nel file, non in una schermata di configurazione da tenere allineata. Gira una sola volta per volta 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.

database/access.json
{
  "permissions": {
    "pratiche.gestisci": { "label": "Prende in carico e chiude le pratiche di chiunque" },
    "clienti.gestisci":  { "label": "Tiene l'anagrafica dei clienti" },
    "persone.gestisci":  { "label": "Invita le persone e assegna i ruoli" }
  },

  "roles": {
    "staff": {
      "label": "Staff",
      "description": "Lavora le pratiche di tutti, tiene l'anagrafica e invita le persone",
      "permissions": ["*"]
    },
    "cliente": {
      "label": "Cliente",
      "description": "Apre le proprie richieste e ne segue l'avanzamento",
      "permissions": [],
      "default": true
    }
  }
}
Quello che non hai scritto

Ruoli e permessi si dichiarano una volta e valgono in ogni schermata, in ogni azione e in ogni chiamata con una chiave API. Non c'è un `if` sul ruolo sparso per il codice: le regole stanno qui, e chi non le rispetta non passa — nemmeno per sbaglio.

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

  "image":  { "base": "debian:bookworm", "packages": ["ffmpeg", "nodejs"] },
  "start":  "node server.mjs",

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

  "secrets": ["STRIPE_KEY"],
  "sleep":   { "enabled": true, "afterMin": 3 },
  "grants":  ["db", "fs"]
}
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.

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'inventario di cosa il progetto espone e a chi: le azioni raggruppate per cartella, la chiamata pronta da copiare, i campi che accetta e — detto in chiaro — chi può chiamarla. Più i lavori interni, che da fuori nessuno può invocare.

studio.spelltech.cloud
La sezione API dello Studio: la griglia delle azioni raggruppate per namespace e il pannello di dettaglio di un'azione aperto a destra.
Ingrandisci
La sezione API dello Studio: la griglia delle azioni raggruppate per namespace 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

Le variabili che il codice legge, coi valori nascosti e da rivelare uno alla volta. 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.

  • 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. I permessi sono un file di dati. Il tema è un file di dati. 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 si cancellano, non si compilano.

  • E lo eietti quando la parte generata non basta

    Un'operazione generata si sostituisce con la tua, 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.

Le librerie

E quello che il codice può importare

Il progetto attinge a un deposito di librerie condiviso: quello che serve c'è già installato, e la schermata Librerie dice esattamente cosa può entrare nel backend e cosa nelle pagine. Le chiavi dei servizi esterni non stanno nel codice: stanno fra i segreti, e il codice le legge da lì.

I tipi devono passare

Il controllo gira sul progetto come girerebbe sul tuo computer, e una pubblicazione con i tipi rotti non parte.

L'agente si rilegge

Dopo aver scritto, controlla: compila, prova l'azione, guarda la schermata. Un errore trovato subito costa un messaggio, uno trovato dopo costa una sera.

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.