fix(teilen): Basis-Adresse per Lebendprobe prüfen, sonst fragen (D81-Nachtrag)

Auf einer Instanz ohne eigenes Backend (GitHub Pages) endete Teilen mit
HTTP 405: Die Vorgabe "eigene Herkunft" (D76-Nachtrag 8) stimmt nur auf der
produktiven Installation. serverBaseOrAsk() prüft die Vorgabe jetzt mit
GET /api/v1/info (D77), bevor gePOSTet wird, und fragt sonst nach der
Server-Adresse; gemerkt wird nur eine Adresse, die die Probe besteht.
Von Pages aus trägt man werkbaum.javagil.de ein — CORS erlaubt es.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-27 05:23:19 +02:00
co-authored by Claude Fable 5
parent 4be6ecb984
commit 67d0ef421b
5 changed files with 79 additions and 6 deletions
+4
View File
@@ -17,6 +17,10 @@ the git history of `docs/examples/werkbaum.werkbaum`. A day can therefore carry
a link without having a note (someone forgot to write one) — but never the
reverse.
## 2026-08-27
- Fix: sharing from an instance without its own backend (GitHub Pages) failed with HTTP 405 — the share button now probes the default address and asks for the server instead
## 2026-08-26
- The document picker moved into the app header: `Werkbaum name` opens the menu — on the phone it is reachable from both panes, and the name takes over the subtitle line
+35
View File
@@ -7006,3 +7006,38 @@ stellt Text und Knopfzustände zurück, ohne das Menü zu schließen; Löschen
eines nicht-aktiven Dokuments lässt den Editor unangetastet; auf dem Telefon
ist der Wähler aus dem Diagramm-Bereich erreichbar und der Wechsel rendert
183 Knoten, langer Name füllt die 335-px-Chipzeile mit Ellipse.
**Nachtrag — vor dem Teilen wird die Basis-Adresse per Lebendprobe geprüft.**
Gemeldet vom ersten echten Druck auf den neuen Teilen-Knopf: „Server-Dokument
nicht geladen: https://mhoennig.github.io/api/v1/documents (HTTP 405)." Die
Ursache liegt nicht im Knopf, sondern in der Vorgabe aus D76-Nachtrag 8: Die
Basis-Adresse ist die **eigene Herkunft** — richtig auf der produktiven
Installation (dort liegt `/api/` per Proxy dahinter, D77), falsch auf jeder
statischen Instanz. GitHub Pages beantwortet den POST mit 405, und die
Meldung führte auf die falsche Fährte („ist die Adresse eine
Dokument-Adresse?"). Der Fehler war schon vor D81 da; der Knopf in der
Titelzeile hat ihn nur sichtbar gemacht — im Menü hat ihn dort schlicht
niemand gedrückt.
**Gefixt mit der Lebendprobe, die es schon gibt:** `GET /api/v1/info` (D77,
gebaut genau als „antwortet hier die Anwendung?"). `serverBaseOrAsk()` prüft
die Vorgabe damit, **bevor** gePOSTet wird; besteht sie nicht, wird
**gefragt** — der vorhandene Dialog samt gemerkter Antwort. Gemerkt wird nur
eine Adresse, die die Probe besteht: Ein Tippfehler klemmt sich nicht fest
(und eine gemerkte Adresse, deren Backend verschwindet, heilt sich beim
nächsten Teilen von selbst — Probe scheitert, es wird neu gefragt). Eine
eingegebene Adresse, die nicht antwortet, wird trotzdem versucht — der
POST-Fehlerpfad nennt dann ehrlich, was nicht erreichbar war, statt dass der
Dialog stumm wieder aufgeht.
**Von Pages aus funktioniert das Teilen damit wirklich:** Das Backend erlaubt
CORS `*` (D76-Nachtrag 7) — wer auf `mhoennig.github.io` arbeitet und im
Dialog `https://werkbaum.javagil.de` einträgt, legt sein Dokument dort ab.
Gemessen aus dem Browser: Die Probe gegen die stabile Instanz besteht
cross-origin.
**Nachgemessen** am Dev-Server (dieselbe Lage wie Pages: keine eigene
`/api/`): Teilen öffnet jetzt den Adress-Dialog statt der 405-Warnung,
Abbruch tut nichts; die Probe gegen das echte Backend liefert
`{name:"editor-backend"}`. `infoUrl()` liegt headless in live.js (39
Live-Tests, +1).
+25 -5
View File
@@ -4866,27 +4866,47 @@ function displayName(){
const SERVER_PARAM = 'server';
const LS_SERVER = 'werkbaum-server';
/* Antwortet unter dieser Basis wirklich ein Werkbaum-Backend? Die Lebendprobe
`/api/v1/info` (D77), bevor blind gePOSTet wird. */
async function probeServer(basis){
try{
const info = await fetchJson(live.infoUrl(basis));
return !!(info && info.name);
}catch(_){ return false; }
}
/* Wohin? Reihenfolge und Begründung stehen in live.js. Was dort nicht
hingehört, ist der letzte Ausweg: fragen und die Antwort merken ein
Dialog ist keine entscheidbare Regel. */
function serverBaseOrAsk(){
Dialog ist keine entscheidbare Regel.
Die Vorgabe eigene Herkunft" stimmt nur, wo das Backend wirklich dahinter
liegt (produktive Installation, D77) auf GitHub Pages oder einer anderen
statischen Instanz gibt es keins, und der POST endete dort mit einem
kryptischen 405 (D81-Nachtrag). Deshalb wird die Vorgabe erst per
Lebendprobe geprüft und sonst GEFRAGT; gemerkt wird nur eine Adresse, die
die Probe besteht ein Tippfehler klemmt sich so nicht fest. Eine
eingegebene Adresse, die nicht antwortet, wird trotzdem versucht: Der
POST-Fehlerpfad nennt dann ehrlich, was nicht erreichbar war. */
async function serverBaseOrAsk(){
const offen = liveState ? liveState.urls.doc : null;
let gemerkt = null;
try{ gemerkt = localStorage.getItem(LS_SERVER); }catch(_){}
const basis = live.serverBase(urlParam(SERVER_PARAM) || gemerkt, offen, location.href);
if(basis) return basis;
if(basis && await probeServer(basis)) return basis;
const eingabe = window.prompt(t('docToServerAsk'), 'https://');
if(!eingabe) return null;
const geprueft = live.serverBase(eingabe, null, location.href);
if(geprueft){ try{ localStorage.setItem(LS_SERVER, geprueft); }catch(_){} }
if(geprueft && await probeServer(geprueft)){
try{ localStorage.setItem(LS_SERVER, geprueft); }catch(_){}
}
return geprueft;
}
async function putOnServer(){
const d = activeDoc();
if(!d) return;
const basis = serverBaseOrAsk();
const basis = await serverBaseOrAsk();
if(!basis) return;
try{
+8
View File
@@ -77,6 +77,14 @@ export function documentsUrl(base){
return String(base).replace(/\/+$/, '') + '/api/v1/documents';
}
/* Lebendprobe (D77): antwortet hier wirklich ein Werkbaum-Backend? Vor dem
Teilen gefragt, denn die Vorgabe „eigene Herkunft" stimmt nur auf der
produktiven Installation — auf einer statischen Instanz (GitHub Pages)
bekäme der POST sonst ein kryptisches 405 (D81-Nachtrag). */
export function infoUrl(base){
return String(base).replace(/\/+$/, '') + '/api/v1/info';
}
/* ------------------------------------------------------------------ Zeilen */
/* Zeilenenden auf LF (SPEC §12). Der Server normalisiert beim Speichern
+7 -1
View File
@@ -1,7 +1,7 @@
import {describe, it, expect} from 'vitest';
import {
liveUrls, normalize, lines, text, computeOps, applyOps,
mapLine, caretToLineCol, lineColToCaret, feedAction, serverBase, documentsUrl,
mapLine, caretToLineCol, lineColToCaret, feedAction, serverBase, documentsUrl, infoUrl,
} from '../src/live.js';
describe('Adressen', () => {
@@ -195,6 +195,12 @@ describe('Basis-Adresse des Backends', () => {
.toBe('https://werkbaum.example/api/v1/documents');
});
it('die Lebendprobe fragt /api/v1/info an der Basis (D81-Nachtrag)', () => {
expect(infoUrl('https://werkbaum.example')).toBe('https://werkbaum.example/api/v1/info');
expect(infoUrl('https://werkbaum.example/unterverzeichnis/'))
.toBe('https://werkbaum.example/unterverzeichnis/api/v1/info');
});
it('der ?server=-Parameter hat Vorrang', () => {
expect(serverBase('http://localhost:8080', 'https://fremd.example/api/v1/documents/x', seite))
.toBe('http://localhost:8080');