The interface
The interface is split in two, and the cut is deliberate:
@reply2social/core— plain TypeScript. Which routes exist, what shape their responses have, and what each “no” means. Zero framework dependencies.@reply2social/svelte— how it is painted. Thin on purpose.
The transport comes in as a parameter and is not resolved inside. How the viewer authenticates belongs to the host: on Antisionista it is a session cookie, on another installation it could be a token. If the core knew about cookies it would be useless on the second installation — and it would be the one module you have to rewrite in order to port it.
The layers, and what each one decides
ClaseDeFallo: what each “no” means
A raw HTTP error is of no use to a screen. 401 and 503 are both “you do not
get through”, and you want to do different things with each.
| Class | When | What the screen should do |
|---|---|---|
sesion | 401 | Send the user to log in again |
permiso | 403 | Say the role is not enough; do not retry |
no_disponible | 503 | “Could not be queried” — not “there is nothing” |
conflicto | 409 | Already exists: explain it, do not retry |
peticion | 400 | Something in the form is wrong |
servidor | 5xx | Retryable |
red | never arrived | Retryable |
contrato | shape does not match | Panel and service on different versions |
contrato exists as a class of its own. When the service returns something
the panel does not understand, what happened is almost always that they are on
different versions —one was deployed and the other was not— and the message
says so. Without that class it showed up as a server error and people looked
for the problem in the wrong place.
One view per screen
Each view is a function that takes the client and returns a readable store
plus its actions. The contract is subscribe, which is the same one Svelte
uses — and that is why the wrapper can be so thin.
| View | Screen | What it cannot get wrong |
|---|---|---|
vista_por_aprobar | To approve | Approving in bulk without saying how many and towards where |
vista_historial | History | Showing consented material and attribution-published material as equals |
vista_archivo | Archive | Sending items through another source’s route |
vista_fuentes | Flows | Drawing in green something that was not tested |
vista_alta | Account sign-up | Retrying by itself forever |
vista_programacion | Scheduling | Saying “active” by reading the configuration instead of the heartbeat |
vista_rescate | Historical rescue | Painting a rate-limit pause as if it were an error |
vista_avisos | Notices | Showing only what was delivered successfully |
Why the rules live in the core and not in the template.
Every one of those rules is a decision that was hard to find, and several came
out of an incident. If they lived in the .svelte file, the day the React
wrapper exists they would have to be reimplemented — and one of them would get
lost. They are in TypeScript, with a test each, so the port inherits the scars
and not only the shape.
An example: a flow’s traffic light
It is the most important rule in the interface, and it fits in a diagram.
Notice that no path on the left branch reaches green. With the data from the listing you can tell that something is broken, but not that it works: for that you have to ask the network. The best thing an untested flow says is “untested”. A green tick at rest would be a promise drawn exactly where people look most.
destino_activo === false, not !destino_activo. The panel and the
service are different processes: during a deployment, the panel talks for a
while to a version of the service that does not send that field yet. With the
simple negation, that undefined would paint every flow as paused — and a
false alarm on every deployment teaches people to ignore the alarm.