Une app Xuda est un ensemble de documents typés, et un seul interpréteur les exécute.
Studio ne génère pas une base de code que vous devez ensuite maintenir. Un projet est un ensemble plat de documents, chacun estampillé docType studio, répartis en douze types de nœuds : composants, tables, unités de données, routes, agents et le reste. L'arborescence du panneau de gauche est dérivée de ces documents au chargement, à partir d'un pointeur parent porté par chacun, et n'est jamais stockée comme une arborescence à part. Ce sont ces mêmes documents qu'un aperçu demande par id, que parcourt un build, et qu'exécute le runtime quand quelqu'un ouvre l'app. Rien entre les deux ne les compile en autre chose.

Types de nœuds à partir desquels un projet est construit
Types d'événements auxquels un composant peut réagir
Types de déploiement pour un même projet
Plugins first-party, répartis en six catégories
Cinq arborescences distinctes, et aucun document arbre nulle part.
Le panneau de gauche ne contient pas une arborescence. Il en contient cinq, et un segment à cinq icônes au-dessus décide laquelle est montée : tables, écrans, état partagé, URLs et agents sont ainsi cinq espaces d'adressage distincts plutôt que cinq branches d'un seul et même arbre. Aucune des cinq n'est stockée comme une arborescence non plus. Chaque nœud est un document portant un unique pointeur parentId et une clé de tri order_ts, et un web worker reconstruit toute la structure au chargement en cherchant les enfants niveau par niveau, en n'admettant que les documents dont le stat vaut 3. Déplacer un nœud vers le haut ou le bas n'est pas un déplacement de fichier : cela enregistre exactement deux documents dont les valeurs order_ts sont échangées. Supprimer une sous-arborescence écrit stat 4 sur chaque document qu'elle contient et les laisse tous en stockage, ce qui explique pourquoi l'arborescence cesse de les afficher et l'index de recherche cesse de les indexer au même instant. Ouvrez un composant et passez à son onglet UI, et une seconde arborescence apparaît sous la première, l'arbre d'éléments de ce composant, avec une poignée déplaçable entre les deux dont la position est mémorisée.
Quatre onglets, c'est vrai pour un composant. Chaque autre type ouvre autre chose.
La version de cette page que vous avez peut-être lue disait que les écrans, les tâches de fond et les points de terminaison API sont le même genre d'objet avec les mêmes quatre onglets. La première moitié est vraie, pas la seconde. L'éditeur construit sa barre d'onglets en fonction du type de nœud, et l'enveloppe se rétrécit ou s'agrandit : un composant obtient quatre onglets, un batch ou une unité de données en obtient trois et n'a aucune UI, une unité API en obtient quatre mais le quatrième est Sortie plutôt que UI, et cinq des douze types n'ouvrent qu'un seul onglet, voire aucun : alert, javascript, route et ai_agent n'ouvrent chacun qu'un seul éditeur, et un dossier n'ouvre rien du tout. La barre n'est affichée que lorsqu'un nœud a plus d'un onglet, donc ces cinq-là arrivent sans barre au-dessus. Quel onglet est ouvert est un paramètre de requête sur l'URL de l'éditeur, et le dernier onglet consulté sur un nœud donné est mémorisé par nœud, donc y revenir vous replace là où vous l'aviez laissé. La deuxième colonne est l'autre moitié de la même grammaire. Le panneau Créer propose un ensemble de types différent selon le nœud sur lequel vous avez fait un clic droit, et un dossier hérite simplement de l'ensemble de la section dans laquelle il se trouve.
Ce que ça ouvre | Où ça peut être créé | |
|---|---|---|
| Composant | Source de données, Champs, Événements, UI | Sous Programmes, ou à l'intérieur d'un autre composant |
| API | Source de données, Champs, Événements, Sortie | Sous Programmes ou toute autre unité, jamais à l'intérieur d'un composant |
| Batch | Source de données, Champs, Événements. Pas d'onglet UI | Programmes, à l'intérieur d'un composant, ou de toute autre unité |
| Récupérer des données (get_data) | Source de données, Champs, Événements. Pas d'onglet UI | Programmes, à l'intérieur d'un composant, ou de toute autre unité |
| Écrire des données (set_data) | Source de données, Champs, Événements. Pas d'onglet UI | Programmes, à l'intérieur d'un composant, ou de toute autre unité |
| Alerte | Un onglet, Alerte | Sous Programmes ou à l'intérieur d'un composant, nulle part ailleurs |
| JavaScript | Un onglet, Éditeur | Sous Programmes ou à l'intérieur d'un composant, nulle part ailleurs |
| Table | Données, Champs, Index | Sous Base de données, où Créer se grise ensuite sur la table elle-même |
| Globales | Champs, Événements | Pas depuis l'arborescence : la racine Globales refuse tout nouvel enfant |
| Route | Un onglet, Route | Sous Routes, le seul type que cette section propose |
| Agent IA (ai_agent) | Un onglet, Agent IA | Sous Agents IA, le seul type que cette section propose |
| Dossier | Rien du tout. Ouvrir un dossier ressort immédiatement | Sous Programmes ou Base de données, ou à l'intérieur d'un composant ou de toute autre unité |
L'assistant écrit dans la même arborescence que vous modifiez à la main, et un mode n'écrit rien du tout.
Décrivez ce que vous voulez dans le panneau IA d'un projet ouvert, et le générateur propose des tables, des composants, des champs et des événements comme des documents ordinaires dans l'arborescence ordinaire, ce qui explique que vous puissiez ensuite ouvrir le résultat et modifier un champ. Les deux commutateurs du panneau ne sont pas le même commutateur : la portée décide de quoi parle la conversation, le mode décide si le tour a le droit d'écrire. À l'intérieur de Studio, le compositeur masque aussi le sélecteur de modèle et le sélecteur d'agent que le reste du tableau de bord affiche, donc il n'y a ni modèle ni agent à choisir.
- Le mode Plan n'écrit rien. Le panneau décrit Auto comme s'exécutant de bout en bout sans interruption, et Plan comme cherchant et proposant une approche sans rien modifier. Le choix voyage dans la requête sous le nom plan_mode, et le serveur le transforme en une méthode différente, avec la sauvegarde désactivée pour tout le tour.
- La portée décide de ce à quoi le tour est lié. En portée générale, la conversation référence l'app ; en portée nœud, elle référence le nœud que vous avez ouvert, comme un studio_node plutôt que comme une app. Changer de portée démarre une nouvelle conversation et recharge la liste des conversations, car les anciennes conversations sont récupérées selon ce qu'elles référencent.
- Les puces de contexte sont des bascules, pas des étiquettes. Jusqu'à trois apparaissent : le programme ouvert dans l'éditeur, un nœud différent sélectionné dans l'arborescence, et l'élément UI sélectionné pendant que l'onglet UI est ouvert. Décochez-en une et elle disparaît de la requête. Décochez les trois et le tour est envoyé sans aucun contexte.
- Le générateur est un seul paquet npm avec des méthodes nommées, et deux de ces méthodes écrivent des plugins. app_builder enregistre des documents studio, app_planner ne le fait pas, et create_plugin_via_ai et modify_plugin_via_ai sont exportées du même paquet. Ce que ce paquet fait en interne n'est pas dans le code source de la plateforme, donc cette page décrit le point d'appel et s'arrête là.
- Vous décrivezDans le panneau IA d'un projet ouvert, en portée générale ou liée à un nœud
- Les puces lient le contexteProgramme ouvert, un nœud sélectionné ailleurs dans l'arborescence, l'élément UI sélectionné
- Les 12 derniers messages font partie du voyageChacun tronqué à 2000 caractères avant envoi
- app_builder enregistre, app_planner nonAccepter un plan le renvoie avec une instruction de l'exécuter de bout en bout
Un tour de construction rend un reçu qui nomme ce qu'il a changé.
La réponse n'est pas le modèle qui raconte ce qu'il a fait. Un formateur dans la plateforme lit le résultat structuré et rédige le message : il compte les documents, les regroupe par type de menu, en liste jusqu'à douze par leur nom avec l'id du document entre crochets, et indique s'ils ont été enregistrés. Un tour de plan affiche des champs entièrement différents, un aperçu, jusqu'à douze unités proposées, huit étapes d'ordre de construction et huit vérifications d'acceptation, et indique toujours qu'il a maintenu le tour en mode planification. Quand l'assistant est bloqué sur quelque chose dont il a besoin de votre part, il ne le demande pas en prose non plus : il émet un bloc de question structuré que le panneau affiche comme un stepper, deux ou trois options par question, avec une ligne Autre en texte libre que l'interface ajoute elle-même. Pendant que le tour s'exécute, le texte de statut alterne parmi une liste fixe de trois messages, une liste par mode, changeant toutes les 1800 millisecondes.
Deux moteurs se cachent derrière un seul contrôle segmenté.
Sur une source de données table, le mode de filtre est Index ou Query, et quand le réglage est absent, le runtime déduit Index si un index est sélectionné et Query sinon. Ce ne sont pas deux habillages d'un même planificateur. Le mode Index lit une clé composée que la table a déjà écrite sur chaque enregistrement. Le mode Query lit un sélecteur assemblé au moment de la requête. Ce que vous pouvez exprimer, et ce qui vous est garanti, change selon la position de ce contrôle.
Il n'y a pas de fiche de propriétés. Chaque propriété est un attribut.
Sélectionnez un élément dans l'arborescence et le panneau de propriétés se remplit de lignes. Ces lignes n'écrivent pas dans un blob de propriétés privé attaché au nœud. Un élément est un id, un nom de balise, une carte d'attributs, des enfants et un contenu texte, et chaque réglage du panneau est littéralement un attribut dans l'espace de noms xu-. Activez Bind et le balisage gagne xu-bind. Tapez ce que vous voulez dans Ajouter un attribut, et ça se place à côté des autres comme un attribut ordinaire. C'est pourquoi une classe écrite à la main, un attribut que vous avez inventé et un contrôle du panneau finissent tous au même endroit, et pourquoi Modifier le HTML vous montre toute la vérité sur un élément plutôt qu'un rendu de celui-ci.
- Le panneau s'ouvre avec trois lignes, xu-show, xu-bind et xu-ref, et s'étoffe à mesure que vous utilisez les accordéons en dessous. Quinze autres noms sont exclus de la liste d'attributs libres car chacun possède déjà son propre contrôle dédié, si bien qu'aucun attribut n'est modifiable à deux endroits à la fois.
- Show et Render ne sont pas deux mots pour dire masquer, et le runtime le dit lui-même dans son propre commentaire. xu-show est une bascule d'affichage et l'élément reste monté. Une porte de rendu fait basculer toute une sous-arborescence dans ou hors du document, donc l'élément est tout simplement absent du DOM.
- Tout attribut a un jumeau calculé : écrivez xu-exp: devant le nom, ou appuyez sur le bouton fx à côté du champ, et la valeur devient une expression qui se recalcule quand ses dépendances changent. C'est générique plutôt qu'une liste soignée de propriétés liables, donc ça marche sur src, sur un attribut que vous avez tapé il y a une minute, et sur des champs qu'un plugin a déclarés. Une valeur et son jumeau calculé occupent le même emplacement plutôt qu'un défaut plus une surcharge, donc Studio signale un élément qui porte les deux et propose de retirer la version statique. Une exception documentée : xu-exp:class remplace l'attribut class statique au lieu de le fusionner.
- La limite, clairement : il n'y a pas d'UI de style. Pas de contrôles d'espacement, pas de contrôles de typographie, pas de sélecteur de couleur, pas d'éditeur de modèle de boîte. Le style, c'est une rangée de puces de classe plus un éditeur CSS brut, donc une valeur de padding est une chaîne de classe Tailwind ou Ionic que vous tapez ou collez, et une classe conditionnelle est un nom de classe mappé à une expression à l'intérieur de xu-class.
Le canevas est une surface de mise en page. Le runtime n'est qu'un bouton plus loin.
Les deux surfaces lisent le même arbre d'éléments, et elles en font des choses différentes. Le canevas de conception rend votre balisage pour que vous puissiez composer et sélectionner, mais il ne fait pas tourner votre app : les composants et widgets imbriqués s'affichent comme des espaces réservés étiquetés, et les conditions sont comparées comme du texte plutôt qu'évaluées. Le bouton bascule de la barre d'outils remplace le canevas par l'aperçu en direct, qui est le vrai runtime, avec une fenêtre Props pour injecter des valeurs de test. Savoir sur quelle surface on se trouve explique la plupart des moments où un écran paraît correct à un endroit et se comporte différemment à l'autre.
Un workflow est une file de jobs, pas une liste d'awaits.
Un événement n'exécute pas ses actions les unes après les autres. Il parcourt le workflow une fois et pousse un job pour chaque étape activée dans un unique tableau tenu sur la session, et une boucle de vidage distincte, qui tourne chaque milliseconde, prend le premier job qui n'est pas déjà marqué occupé et l'exécute. L'ordre est l'ordre du tableau, de haut en bas. Ce qui déclenche l'étape suivante n'est pas la fin de l'étape précédente, c'est la suppression par l'étape précédente de son propre job. Le drapeau async n'est qu'une ligne à l'intérieur de cette boucle.
Vingt-quatre actions en neuf groupes, et l'argument que chacune lit.
Le sélecteur Ajouter une action est construit à partir d'un seul fichier catalogue, et en dehors des entrées IA, chaque action s'accorde de trois façons : le catalogue, la carte des gestionnaires dans le runtime, et le registre du validateur qui enregistre la clé de charge utile que chaque action lit. Ce registre est la colonne de droite. Deux choses avant le tableau. Invoke Action propose onze valeurs dans son menu déroulant et neuf d'entre elles fonctionnent, car act_close et act_back sont commentées à la fois dans le runtime et dans la liste blanche du validateur. Et sept actions d'éléments UI plus anciennes sont carrément rejetées comme dépréciées plutôt que tolérées silencieusement. Vous pouvez aussi créer une action sans le sélecteur : glissez un nœud de l'arborescence sur le workflow et le type de nœud décide de l'action, donc un nœud get data devient Get Data et un composant devient Call Page. Déposez-le sur une ligne qui existe déjà et cela reciblera cette ligne plutôt que d'en ajouter une nouvelle.
Les actions du groupe | Ce que l'action lit | |
|---|---|---|
| Navigation, 3 | Call Page, Call Modal, Call Popover | Chacune nomme le composant à ouvrir, et c'est toute la charge utile. |
| Base de données, 3 | Get Data, Set Data, Batch | L'unité de source de données à exécuter. Set Data et Batch nomment aussi le champ où atterrit leur résultat. |
| JavaScript, 4 | Call Native JS, Call Evaluate JS, Execute Native JS, Execute Evaluate JS | Les deux actions Call nomment une unité programme JavaScript de l'arborescence. Les deux actions Execute portent le code lui-même, tapé directement dans l'action. |
| Gestionnaires d'événements, 4 | Raise Event, Emit Event, Invoke Action, Delay | Raise Event nomme un événement plus ses paramètres, chacun une valeur littérale ou une expression. Invoke Action lit l'une des neuf valeurs autorisées. Delay lit un nombre de millisecondes. |
| Utilitaires UI, 4 | Call Alert, Alert, Loader On, Loader Off | Call Alert nomme une unité alerte. Alert porte son propre mode d'affichage, type, titre et corps. Les deux actions loader ne lisent rien. |
| API, 3 | Call Project API, Call System API, Call External API | Une API projet nomme une unité de votre propre arborescence, une API système nomme une méthode, une API externe prend une URL et une méthode HTTP. |
| Dataset, 1 | Update | Une valeur, écrite comme « @champ : expression ; @autre : expression », ou comme un objet JSON5. |
| Plugins, 1 | Call Library | Le nom du plugin et la méthode dessus, donc une bibliothèque achetée se lit comme n'importe quelle autre action de ce tableau. |
| Commentaire, 1 | Comment | Rien. Le registre le décrit comme documentation uniquement, et le gestionnaire du runtime ne fait rien d'autre que supprimer son propre job. |
L'échappatoire est un chemin, pas une case de texte.
Sept endroits vous laissent arrêter de configurer et commencer à écrire, et le plus profond d'entre eux n'est même pas une action : un programme JavaScript est sa propre unité dans l'arborescence, avec sa propre sortie de build et un ensemble documenté de choses qui lui sont fournies. Deux limites à mentionner dans le même souffle. Le validateur ne parse pas le corps JavaScript, donc une erreur de syntaxe surgit du build comme une compilation échouée plutôt que du vérificateur, qui ne fait qu'avertir quand le script est vide. Et un script en ligne ou un attribut style sur un élément ne doit contenir aucun caractère guillemet, aucun.
- Le corps d'un programme reçoit dix arguments nommés : params, xu, SESSION_ID, SESSION_OBJ, jobNoP, $container, func, glb, APP_OBJ et evt. Sans import ni export de premier niveau, il est enveloppé dans une fonction async, donc le top level await fonctionne.
- Écrivez un import ou un export et il devient un vrai module ES à la place. Les spécificateurs relatifs sont réécrits par rapport au dossier scripts de ce programme, donc une unité peut importer ses propres fichiers frères.
- Enregistrer une unité JavaScript déclenche un build Vite sans bloquer la sauvegarde. À l'exécution, le moteur préfère le module compilé, avec cache invalidé par build, ne charge une feuille de style que si le module en exporte une, et se rabat sur l'exécution de la source avec le code d'avertissement RUN_MSG_JS_010.
- L'objet xu qu'un script reçoit est une API à quinze méthodes : lire et écrire un champ, invoquer un événement, quatre appels base de données, appeler un autre script, trois sortes d'appel API, alerter, journaliser, et lire ou écrire sur le drive. Le code en ligne a une forme différente encore, enveloppé comme une fonction async et appelé avec l'élément et l'événement navigateur.
L'aperçu est un pair de votre éditeur, et il a le droit de renoncer à vous.
Une session d'aperçu s'ouvre sur un canal nommé d'après votre jeton de session et demande à votre onglet ouvert des documents un id à la fois. Studio répond depuis la mémoire avec une copie du document présent sur votre écran, donc il n'y a aucun bundler nulle part sur ce chemin, et un changement enregistré est présent dans la session en cours immédiatement. Il y a aussi une échéance. Si votre onglet n'a pas répondu en cinq secondes, cette session bascule vers la copie compilée sur le serveur et ne vous redemandera plus jamais pour le reste de sa vie.
La boîte de commande, c'est eval, exécuté à l'intérieur de la page que vous prévisualisez.
Il n'y a pas de vocabulaire de commandes à apprendre, car il n'y a pas d'ensemble de commandes. Ce que vous tapez est envoyé à l'aperçu comme une seule chaîne, évalué là-bas, et le résultat est renvoyé et affiché comme une arborescence sur deux niveaux de profondeur. Tout ce que la page en cours peut voir, vous pouvez le voir : l'objet session, les sources de données, les utilitaires du runtime. Une erreur levée revient avec un drapeau d'erreur et s'affiche en rouge. Inspect est l'autre moitié : activez-le, chaque élément portant un id ui se contourne au survol, et cliquer sur l'un renvoie le composant qui le possède et le nœud à l'intérieur, avec un bouton qui ouvre ce composant sur son onglet UI et sélectionne le nœud dans l'arborescence. C'est un pixel jusqu'à un nœud en deux clics.
Un enregistrement est une écriture locale. La version est écrite par autre chose, après coup.
Il n'y a pas d'enregistrement automatique. Appuyer sur enregistrer remet le document à un worker, qui l'estampille et l'écrit dans une base de données à l'intérieur de votre navigateur, et c'est tout ce que fait votre frappe de touche. Tout ce qui vient après relève d'une autre machinerie : une diffusion vers vos autres onglets, une réplication vers la base de données de l'app, et un processus serveur qui surveille cette base de données, lequel est ce qui frappe réellement une version. L'ordre compte, car il indique ce qui survit à un réseau coupé et ce qui ne survit pas.
La pastille a quatre états, et l'un d'eux dit que votre onglet n'est pas à jour.
L'étiquette à côté de l'éditeur est dérivée, pas stockée : elle compare la révision que vous avez enregistrée au dernier instantané sur le serveur. À jour signifie qu'un instantané exact existe pour votre révision, ou que la différence n'est que des métadonnées de build. Enregistrement en cours signifie que votre révision est en avance et que la capture n'a pas encore atterri. En retard signifie que votre révision enregistrée est plus ancienne que le dernier instantané, donc cette session de navigateur a dormi pendant des révisions, et la fenêtre le dit dans une bannière plutôt que de prétendre que vous êtes à jour. Inconnu signifie même génération, contenu différent, ce que la source appelle une branche divergente. Sélectionner une ligne plus ancienne la prévisualise sur place, votre copie de travail étant conservée par référence, et pendant qu'un aperçu est affiché, les deux chemins d'enregistrement refusent le document purement et simplement. Restaurer est la seule action qui écrit : elle relit d'abord la révision en direct depuis le serveur, demande avant d'écraser un changement arrivé pendant que vous regardiez, redemande avant d'abandonner vos modifications non enregistrées, puis se chaîne sur la révision connue la plus récente afin que la restauration atterrisse proprement en tête.
enregistrer, capturé sur le serveur un instant plus tard
Ce qui n'est pas là, intégralement : pas de branches, pas de vue comparaison ou diff, pas de fusion, pas de messages de commit. L'historique est par programme plutôt que par projet. Les numéros de version sont positionnels, donc le v3 d'aujourd'hui n'est pas le v3 de la semaine prochaine. Cinquante instantanés sont conservés par document et le reste est supprimé plutôt qu'archivé. La capture est asynchrone et côté serveur, donc un enregistrement frais se lit comme enregistrement en cours jusqu'à ce qu'il atterrisse. Et l'annulation est un système différent qui ne partage rien avec celui-ci : cinquante étapes par document, regroupées à quatre cents millisecondes pour qu'un glissement ou une rafale de frappe soit une seule étape, cantonnée au nœud sélectionné, tenue en mémoire navigateur uniquement, et perdue au rechargement. Après un rechargement, ce rail est le seul chemin de retour.
Il y a un verrou. Il n'y a pas de fusion.
Le verrou est un check-in, et il est par nœud. L'enregistrement en dépend : un nœud verrouillé par quelqu'un d'autre est refusé avec le nom de qui le détient, et un nœud que personne n'a verrouillé est refusé avec Veuillez faire un check-in, après avoir remonté la chaîne des parents pour voir si quelque chose au-dessus est détenu. Les brouillons sont exemptés, tout comme une app qui n'a jamais été enregistrée. Ce qui n'est pas verrouillé, c'est le résultat une fois qu'une révision de quelqu'un d'autre atteint effectivement un document que vous avez ouvert. Studio tranche entre exactement trois cas, et il calcule si vous avez des modifications non enregistrées avant de mettre à jour sa base de référence, ce qui est l'ordre qui rend la réponse fiable.
- 01
Votre copie est propre, donc vous prenez la leur en entier
Rien de ce qui est à vous n'est en jeu, donc Studio avance directement le document ouvert jusqu'à leur révision en bloc et rafraîchit le drapeau d'onglet immédiatement plutôt que d'attendre l'observateur limité. Vous le découvrez parce que le contenu a changé sous vos yeux, pas parce qu'une boîte de dialogue vous l'a demandé.
- 02
Vous avez des modifications, et leur écriture était le compilateur
Studio compare les deux documents avec la liste de révisions, les métadonnées studio, le résultat de build, l'erreur de build, les trois horodatages et les dépendances de script retirés. Quand ce qui reste est identique, la révision arrivante est le worker de build enregistrant sa propre sortie. Vos modifications restent exactement là où elles sont, seuls la révision et les champs de build sont adoptés, et on ne vous en informe pas, car rien ne vous est arrivé.
- 03
Vous avez des modifications, et leur contenu diffère vraiment
Chacune de vos modifications est conservée et seule la révision est incrémentée, donc votre prochain enregistrement se chaîne sur le leur plutôt que d'entrer en collision. Puis vous êtes averti pendant douze secondes : un collaborateur a enregistré ce document, et vos modifications non enregistrées écraseront les siennes si vous enregistrez. L'avertissement a exactement une action, abandonner les miennes et utiliser les leurs, qui remplace votre copie de travail par le document serveur. Ignorez-le et enregistrez, et c'est votre version qui survit. Il n'y a pas de fusion au niveau des champs, pas de diff, pas de présence et pas de curseurs partagés.
Un seul fichier de vérification, exécuté dans votre navigateur ou sur le serveur selon où vous êtes.
La validation n'est pas un service ailleurs. C'est un seul fichier, runtime/js/modules/xuda-studio-checker.mjs, 4714 lignes, importé directement par le front-end de Studio et importé par chemin sur le serveur par le module app. Studio décide au moment de l'appel quel hôte l'exécute. En ligne, sur un projet qui n'est pas un brouillon, il déclenche run_programs_checker comme tâche de fond. Hors ligne, ou dans un brouillon, il exécute la même vérification sur chaque programme dans le navigateur et valide un résultat de même forme. Les constats se répartissent en erreurs et avertissements sur rien de plus qu'un champ type valant E ou W. Puis la partie honnête : cela ne bloque pas un build. build_app prend ignore_errors avec un défaut à true, aucun appelant du produit n'envoie jamais false, et le total d'erreurs est écrit dans la release elle-même, donc chaque version enregistrée porte le nombre d'erreurs avec lesquelles elle a été livrée.
- Dix-huit fonctions de vérification se répartissent sur onze types de programmes : globals, table, component, javascript, get_data, set_data, batch, api, alert, route et ai_agent. Un dossier est un no-op délibéré, et tout ce qui n'est pas reconnu lève CHK_MSG_GEN_050.
- Une exécution serveur écrit un document de vérification par programme, indexé sur la révision de ce programme, donc un programme auquel personne n'a touché depuis le dernier passage est carrément sauté plutôt que revérifié.
- Chaque code du catalogue généré porte les mêmes champs : titre, gravité, domaine, catégorie, résumé, causes, étapes de résolution, un slug d'aide, des schémas hérités et des codes liés. Il est généré à partir du registre d'erreurs, pas écrit à la main.
- Les deux hôtes ne produisent pas une sortie identique. L'exécution serveur est initialisée avec la liste des déploiements et peut marquer un programme comme utilisé par un déploiement. L'exécution navigateur ne l'est pas, donc le verdict non utilisé diffère entre les deux.
Un build et un déploiement sont des objets différents, et une release se trouve entre les deux.
Construire écrit un instantané numéroté. Déployer pointe une machine vers l'un d'eux. Le numéro sur le badge, #37, est app_build_id, un compteur par projet qui s'incrémente de un à chaque exécution de build_app. Le 1.4.0 juste à côté est une chaîne distincte que vous tapez, validée par un simple motif majeur, mineur, patch dans le badge lui-même. Ils évoluent indépendamment, et tous deux sont estampillés sur le document de déploiement afin qu'un déploiement enregistre toujours exactement quel instantané il sert.
Appuyer sur déployer n'installe rien.
L'appel de déploiement est la première moitié d'une poignée de main. Il crée des enregistrements, demande une machine et retourne pendant que la machine est encore en cours de création. L'installation démarre quand la machine elle-même rappelle la plateforme, ce qui explique pourquoi la barre de progression peut rester immobile un moment au milieu et pourquoi l'échelle de statuts a bel et bien un échelon d'attente : 1 créé, 2 en installation ou en attente, 3 actif, 4 échoué ou terminé.
- 01
Le formulaire retourne avant que quoi que ce soit existe
deploy_app écrit le document de déploiement, éventuellement un document datacenter, demande à l'hôte de créer une machine de façon asynchrone, et vous retourne le contrôle. Rien n'a été installé et rien n'a été copié. Le déploiement se trouve au code de statut 2 sous la description serveur de ferme en attente d'installation, et il n'y a vraiment rien de plus à montrer avant que l'étape suivante se produise d'elle-même.
- 02
La machine rappelle la plateforme
Le premier démarrage sur la nouvelle machine interroge un webhook sur la plateforme portant l'id de l'app. Ce gestionnaire rouvre le même job que vous surveillez déjà, remplace sa liste d'étapes par la liste d'installation à vingt-quatre entrées, et exécute install_hosting_resources. Le job que vous avez démarré pour la phase un est le job qui rend compte de la phase deux, ce qui explique pourquoi les noms d'étapes changent sous vos yeux en cours de route.
- 03
L'installation s'exécute, réessaie si nécessaire, et atterrit au statut 3
L'installation est enveloppée dans quatre tentatives avec un délai de trente secondes, car une machine qui vient d'être créée peut encore redémarrer quand la première connexion SSH arrive. Elle écrit le fichier écosystème pm2, exécute pm2 delete puis pm2 start dessus, et se termine au code de statut 3, serveur prêt. Cette page ne cite aucune durée pour tout cela, car rien dans le code source n'en enregistre une et nous ne l'avons pas mesurée.
Seize types de déploiement, et ce que chacun exécute réellement.
Le même projet se déploie en seize types de déploiement déclarés, en quatre groupes : six web, cinq serveur, trois desktop et deux mobile. Chaque type déclare aussi son propre ensemble d'onglets à partir d'une liste de base de quatorze onglets, donc un déploiement cron abandonne launch, domain, firewall, analytics et user assist et se résout à neuf onglets, tandis qu'une MPA garde les quatorze et les cinq types artefact insèrent un onglet publish à la place. Regroupés selon ce que l'installation écrit réellement dans le fichier écosystème pm2, quinze d'entre eux se ramènent à cinq formes. Le seizième, mcp, est déclaré aux côtés des autres et n'a pas d'entrée écosystème propre que nous ayons pu tracer.
Ce qui tourne sur la machine | Ce que ça lance | |
|---|---|---|
| Web : spa, mpa, pwa, embed | Une app pm2 nommée webserver, mode d'exécution cluster avec instances réglées sur max, plus une unité systemd pour le routeur websocket | Le bundle runtime estampillé, en HTTPS sur le port 443, lié sur un socket double pile |
| Web : preview | La même app webserver, et une seconde app pm2 nommée preview | Une sortie serveur Nuxt sur le port 3023 à côté de l'app elle-même |
| Web : user group | La même app webserver, et une seconde app pm2 nommée user_group | Une sortie serveur Nuxt sur le port 3022 pour la surface groupe |
| Serveur : cron, api, node, emitter | Une seule app pm2 en mode d'exécution fork, exécutant le fichier d'entrée de ce type, xuda-cron.mjs ou xuda-api.mjs et ainsi de suite | Le bundle serveur pour ce build. Un déclenchement cron forke un enfant, attend qu'il se déclare prêt, lui envoie l'exécution, et journalise ce qui revient |
| Artefacts : ios, android, macos, windows, linux | Rien. Le chemin macOS contourne délibérément l'appel de déploiement et écrit un document de déploiement sans machine d'hébergement derrière | Un onglet publish, et un envoi à un service de build externe. Nous avons tracé assez pour savoir que c'est un pipeline différent, pas assez pour le décrire ici |
Un interpréteur, deux côtés, un seul appel.
Ce qu'un déploiement livre réellement n'est ni du code source généré ni un blob compilé. C'est un bundle interpréteur plus votre arborescence de programmes en JSON, et le même interpréteur tourne des deux côtés du fil. Dans le navigateur, c'est une balise script et un constructeur. Sur le serveur, c'est un import dynamique du bundle frère et un drapeau. Quand une page est rendue côté serveur, le serveur demande au worker d'exécuter un programme jusqu'à une chaîne, et récupère à la fois du balisage et une charge utile. La charge utile est intégrée dans la page, et l'élément de montage est marqué avec le mode de rendu et l'activation client que le serveur a résolus pour lui.
La catégorie dans le nom du paquet décide où un plugin apparaît.
Les 62 plugins first-party sont tous nommés de la même façon, et la catégorie dans ce nom se trouve aussi sur l'enregistrement du plugin. C'est toute la règle de routage : installez-en un et il apparaît dans le sélecteur de source de données, la barre d'outils d'insertion, le sélecteur d'icônes ou la liste d'actions de workflow, sans rien à raccorder. Une installation n'est pas non plus un simple drapeau de fonctionnalité. Le serveur écrit un enregistrement sur le projet portant la version, le type et le manifeste de fichiers, exécute un vrai npm install dans le dossier de plugins pour ce projet, synchronise ce dossier vers les serveurs, et copie tout l'arbre de fichiers du paquet dans la base de données du projet, indexé par chemin relatif.
Neuf choses que Studio ne fait pas.
- La boîte de prompt sur l'écran de démarrage de Studio ne construit rien : elle envoie une simple requête de chat plafonnée à 1024 jetons, et avec les messages masqués, la réponse n'est même pas affichée, car le générateur vit dans le panneau IA d'un projet déjà ouvert.
- Choisir Modèle n'ouvre pas une galerie dans l'éditeur : cela ouvre l'assistant de création du tableau de bord dans un second onglet de navigateur, et le projet qu'il crée par fork est un instantané sans aucun lien vers sa source par la suite.
- Un brouillon n'est pas sur un serveur : il vit dans un profil de navigateur sur un appareil jusqu'à ce que vous appuyiez sur Enregistrer, et effacer les données du site y met fin.
- La revendication de check-in qui conditionne l'enregistrement n'est pas un bail serveur : c'est un champ sur le document, donc un enregistrement qui n'atteint jamais le serveur ne le libère jamais, et rien ne fusionne deux modifications pour vous.
- Rien ne peut faire échouer un build sur des erreurs de validation : le vérificateur s'exécute d'abord et son total d'erreurs est écrit dans la release, mais aucun appelant ne demande au build de refuser.
- Un build terminé ne touche à rien de ce qui est déjà en ligne, et deux déploiements d'un même projet peuvent rester sur des numéros de build différents aussi longtemps que vous les laissez.
- Il n'y a pas de déploiement progressif : le badge de build peut afficher une bannière en préproduction mais pas en ligne, mais un build normal attribue au numéro en préproduction et au numéro en ligne la même valeur, donc cette bannière ne peut jamais s'afficher.
- Il n'y a pas d'allègement du runtime par app, donc chaque déploiement livre le même bundle interpréteur complet pour son build et seul le JSON des programmes diffère.
- Un déploiement planifié ne fera tourner aucun écran, seulement des programmes batch, get_data et set_data, et une étape JavaScript côté serveur est stoppée à soixante secondes.
Les questions que vous êtes en train de vous poser
Chacune de ces sept réponses se termine sur une limite. Elles sont ici parce que vous rencontreriez de toute façon les sept dès la première semaine.
Voyez Studio ouvrir un projet et remplir l'arborescence.
Ouvrez un projet vide et regardez ce qui s'écrit.
Deux documents, une route, et l'éditeur est ouvert. Rien n'atteint un serveur avant que vous enregistriez, et rien n'atteint un visiteur avant que vous construisiez un build, puis déplaciez délibérément un déploiement vers ce build.