Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8403b4fa08 | ||
|
|
757e32229d | ||
|
|
4c995f4c65 | ||
|
|
c950940e22 | ||
|
|
858dfd22e1 | ||
|
|
ab4a4dbd9d | ||
|
|
0bc1aca9bc | ||
|
|
515727ab9b | ||
|
|
3f375bca2a | ||
|
|
483af904af | ||
|
|
bfe0c75106 | ||
|
|
2a96ad3b1d | ||
|
|
12a29b29f6 | ||
|
|
8feab0188c | ||
|
|
fc44efabbb | ||
|
|
3ed09cbc0f | ||
|
|
a4f27bdbe7 | ||
|
|
05d1903045 | ||
|
|
b5b4a00b75 | ||
|
|
97f997840c | ||
|
|
474851b3bb | ||
|
|
4dfa8fd74c | ||
|
|
e709310e70 | ||
|
|
4d0c8ad934 | ||
|
|
b609e8913c |
+11
-1
@@ -6,7 +6,7 @@ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/),
|
|||||||
et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
> **En cours de développement** : les changements à venir sont listés dans la section
|
> **En cours de développement** : les changements à venir sont listés dans la section
|
||||||
> [Unreleased](#unreleased). La dernière version livrée est **2.82.1**.
|
> [Unreleased](#unreleased). La dernière version livrée est **2.82.4**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -14,6 +14,16 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [2.82.4] — 2026-10-28
|
||||||
|
|
||||||
|
- **Recherche (PR #52)** : texte parasite du mécanisme de thème retiré du haut de la page `/recherche` (lignes 28-43 de `frontend/recherche.html`, hors commentaire).
|
||||||
|
- **Démarrage (PR #53)** : écran de chargement plein écran `#boot-splash` retiré au démarrage d'ObsiGate (CSS, div et script de pilotage), sans écran de remplacement.
|
||||||
|
|
||||||
|
## [2.82.3] — 2026-10-28
|
||||||
|
|
||||||
|
- **Recherche (PR #49)** : la page `/recherche` applique désormais le thème ObsiGate.
|
||||||
|
- **CORS recherche (PR #51)** : la recherche ObsiGate autorise désormais la console Spark (agent prod+dev) en inter-origines.
|
||||||
|
|
||||||
## [2.82.2] — 2026-10-28
|
## [2.82.2] — 2026-10-28
|
||||||
|
|
||||||
- **Recherche (#68)** : l'onglet Console Spark de `/recherche` appelle désormais `GET /api/v1/recherche` (au lieu de `/api/search`) et reconstruit les liens profonds console (`?section=…&objet=…`, `disc=`, `activite=`) — les résultats console réels s'affichent enfin dans la page hôte ; barre de `/recherche` re-vendangée depuis `index.html` (5e onglet Console Spark) et `connect-src` CSP étendu à l'origine agent.
|
- **Recherche (#68)** : l'onglet Console Spark de `/recherche` appelle désormais `GET /api/v1/recherche` (au lieu de `/api/search`) et reconstruit les liens profonds console (`?section=…&objet=…`, `disc=`, `activite=`) — les résultats console réels s'affichent enfin dans la page hôte ; barre de `/recherche` re-vendangée depuis `index.html` (5e onglet Console Spark) et `connect-src` CSP étendu à l'origine agent.
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ Ce module n'autorise donc que :
|
|||||||
- le chemin ``/api/search`` (jamais les autres routes, jamais les
|
- le chemin ``/api/search`` (jamais les autres routes, jamais les
|
||||||
écritures) ;
|
écritures) ;
|
||||||
- les origines sœurs : les origines officielles **prod ET dev,
|
- les origines sœurs : les origines officielles **prod ET dev,
|
||||||
listées explicitement** (``PROD_ORIGINS | DEV_ORIGINS``), **plus**
|
listées explicitement** (``PROD_ORIGINS | DEV_ORIGINS``), **plus
|
||||||
|
les origines de la console Spark** (``AGENT_ORIGINS`` : agent prod
|
||||||
|
ET dev — fédération de la recherche depuis la console,
|
||||||
|
2026-10-28), **plus**
|
||||||
celles de ``backend.draco_config`` (``urls_publiques()`` :
|
celles de ``backend.draco_config`` (``urls_publiques()`` :
|
||||||
explicite > dérivation par l'hôte de l'agent > défaut officiel)
|
explicite > dérivation par l'hôte de l'agent > défaut officiel)
|
||||||
— jamais ``*``. Correctif 2026-10-28 (fusion #68/#69, matrice
|
— jamais ``*``. Correctif 2026-10-28 (fusion #68/#69, matrice
|
||||||
@@ -49,6 +52,12 @@ DEV_ORIGINS = frozenset({
|
|||||||
"https://kanbante-dev.dracodev.net",
|
"https://kanbante-dev.dracodev.net",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
AGENT_ORIGINS = frozenset({
|
||||||
|
"https://agent.dracodev.net",
|
||||||
|
"https://agent-dev.dracodev.net",
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
# Clés de draco_config.urls_publiques() qui sont des applications sœurs.
|
# Clés de draco_config.urls_publiques() qui sont des applications sœurs.
|
||||||
_SOEUR_KEYS = ("obsigate", "flowdeck", "tiktak", "kanbante")
|
_SOEUR_KEYS = ("obsigate", "flowdeck", "tiktak", "kanbante")
|
||||||
|
|
||||||
@@ -113,7 +122,7 @@ def sister_origins(resolved: dict | None = None, request_host: str | None = None
|
|||||||
# l'environnement de la requête, désormais inclus d'office).
|
# l'environnement de la requête, désormais inclus d'office).
|
||||||
# ``resolved`` n'ajoute que des origines de configuration sans
|
# ``resolved`` n'ajoute que des origines de configuration sans
|
||||||
# jamais retirer une origine officielle.
|
# jamais retirer une origine officielle.
|
||||||
origins |= PROD_ORIGINS | DEV_ORIGINS
|
origins |= PROD_ORIGINS | DEV_ORIGINS | AGENT_ORIGINS
|
||||||
return frozenset(origins)
|
return frozenset(origins)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Generated
+1
-1
@@ -2626,7 +2626,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "obsigate-desktop"
|
name = "obsigate-desktop"
|
||||||
version = "2.82.2"
|
version = "2.82.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "obsigate-desktop"
|
name = "obsigate-desktop"
|
||||||
version = "2.82.2"
|
version = "2.82.4"
|
||||||
description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian"
|
description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian"
|
||||||
authors = ["Bruno Charest"]
|
authors = ["Bruno Charest"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json",
|
"$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json",
|
||||||
"productName": "ObsiGate",
|
"productName": "ObsiGate",
|
||||||
"version": "2.82.2",
|
"version": "2.82.4",
|
||||||
"identifier": "com.obsigate.desktop",
|
"identifier": "com.obsigate.desktop",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../frontend",
|
"frontendDist": "../frontend",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# ObsiGate — Roadmap
|
# ObsiGate — Roadmap
|
||||||
|
|
||||||
> **Version :** 2.82.2 | **Dernière mise à jour :** 2026-10-28
|
> **Version :** 2.82.4 | **Dernière mise à jour :** 2026-10-28
|
||||||
> **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact
|
> **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact
|
||||||
> vers les fonctionnalités livrées.
|
> vers les fonctionnalités livrées.
|
||||||
> - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)**
|
> - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)**
|
||||||
|
|||||||
+1
-66
@@ -47,33 +47,6 @@
|
|||||||
href="/static/icons/icon-192x192.svg"
|
href="/static/icons/icon-192x192.svg"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Boot splash CSS — inline so it applies BEFORE /static/style.css is
|
|
||||||
available (desktop: embedded page served from tauri:// has no /static) -->
|
|
||||||
<style id="boot-splash-css">
|
|
||||||
body:not(.app-booted) > *:not(#boot-splash) { display: none !important; }
|
|
||||||
#boot-splash {
|
|
||||||
position: fixed; inset: 0; z-index: 2147483647;
|
|
||||||
display: flex; align-items: center; justify-content: center;
|
|
||||||
background: radial-gradient(1200px 600px at 50% -10%, #26272b 0%, #17181a 60%);
|
|
||||||
color: #eceeeb; font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
|
|
||||||
}
|
|
||||||
body.app-booted #boot-splash { display: none; }
|
|
||||||
.bs-card { display: flex; flex-direction: column; align-items: center; gap: 14px; }
|
|
||||||
.bs-logo {
|
|
||||||
width: 72px; height: 72px; display: flex; align-items: center; justify-content: center;
|
|
||||||
font-size: 38px; background: #1f2023; border: 1px solid #33353a; border-radius: 18px;
|
|
||||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.45);
|
|
||||||
}
|
|
||||||
.bs-title { font-size: 22px; font-weight: 600; letter-spacing: 0.3px; }
|
|
||||||
.bs-spinner {
|
|
||||||
width: 34px; height: 34px; border-radius: 50%;
|
|
||||||
border: 3px solid rgba(124, 58, 237, 0.25); border-top-color: #7c3aed;
|
|
||||||
animation: bs-spin 0.9s linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes bs-spin { to { transform: rotate(360deg); } }
|
|
||||||
.bs-status { font-size: 13px; color: #9aa3b2; min-height: 18px; }
|
|
||||||
@media (prefers-reduced-motion: reduce) { .bs-spinner { animation-duration: 1.8s; } }
|
|
||||||
</style>
|
|
||||||
<link rel="stylesheet" href="/static/style.css" />
|
<link rel="stylesheet" href="/static/style.css" />
|
||||||
<link rel="stylesheet" href="/static/draco-barre.css" />
|
<link rel="stylesheet" href="/static/draco-barre.css" />
|
||||||
<link rel="stylesheet" href="/static/draco-alertes.css" />
|
<link rel="stylesheet" href="/static/draco-alertes.css" />
|
||||||
@@ -89,7 +62,7 @@
|
|||||||
id="hljs-theme-light"
|
id="hljs-theme-light"
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
<!-- defer: ne bloque pas le premier paint (le splash inline s'affiche immédiatement) -->
|
<!-- defer: ne bloque pas le premier paint -->
|
||||||
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||||
<!-- Langues highlight.js hors bundle "common" (enregistrement auto apres le core) -->
|
<!-- Langues highlight.js hors bundle "common" (enregistrement auto apres le core) -->
|
||||||
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/dockerfile.min.js"></script>
|
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/dockerfile.min.js"></script>
|
||||||
@@ -226,44 +199,6 @@
|
|||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body class="has-draco-bar">
|
<body class="has-draco-bar">
|
||||||
<!-- Boot splash : visible tant que l'app n'a pas booté avec son CSS.
|
|
||||||
Sur desktop, la page embarquée (tauri://) n'a pas /static/style.css →
|
|
||||||
sans ce splash, le squelette HTML brut serait visible pendant le
|
|
||||||
démarrage du backend (FOUC 3-10 s). Styles 100% inline. -->
|
|
||||||
<div id="boot-splash" role="status" aria-live="polite">
|
|
||||||
<div class="bs-card">
|
|
||||||
<div class="bs-logo">📖</div>
|
|
||||||
<div class="bs-title">ObsiGate</div>
|
|
||||||
<div class="bs-spinner" aria-hidden="true"></div>
|
|
||||||
<div class="bs-status" id="boot-splash-status">Démarrage…</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
var done = false;
|
|
||||||
function bootReady() {
|
|
||||||
if (done) return;
|
|
||||||
done = true;
|
|
||||||
document.body.classList.add("app-booted");
|
|
||||||
var s = document.getElementById("boot-splash");
|
|
||||||
if (s && s.parentNode) s.parentNode.removeChild(s);
|
|
||||||
}
|
|
||||||
window.__obsigateBootReady = bootReady;
|
|
||||||
window.__setBootStatus = function (txt) {
|
|
||||||
var el = document.getElementById("boot-splash-status");
|
|
||||||
if (el) el.textContent = txt;
|
|
||||||
};
|
|
||||||
var poll = setInterval(function () {
|
|
||||||
if (window.__OBSIGATE_BOOTED) { clearInterval(poll); bootReady(); }
|
|
||||||
}, 80);
|
|
||||||
// Filet de sécurité : si le CSS principal a chargé (origine backend réelle)
|
|
||||||
// mais que le JS a échoué, révéler l'app après 10 s au lieu d'un splash infini.
|
|
||||||
setTimeout(function () {
|
|
||||||
var link = document.querySelector('link[href*="style.css"]');
|
|
||||||
if (link && link.sheet) { clearInterval(poll); bootReady(); }
|
|
||||||
}, 10000);
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
<div class="search-progress-bar" id="search-progress-bar">
|
<div class="search-progress-bar" id="search-progress-bar">
|
||||||
<div class="search-progress-bar__fill"></div>
|
<div class="search-progress-bar__fill"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+2
-10
@@ -120,16 +120,8 @@ async function init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", async () => {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
try {
|
await init();
|
||||||
await init();
|
Sync.init();
|
||||||
Sync.init();
|
|
||||||
} finally {
|
|
||||||
// Signale au splash de démarrage (index.html #boot-splash) que l'app est
|
|
||||||
// initialisée : le splash est retiré et le squelette stylé révélé.
|
|
||||||
// Set AFTER init() resolves so every handler (search, tabs, …) is bound.
|
|
||||||
window.__OBSIGATE_BOOTED = true;
|
|
||||||
if (typeof window.__obsigateBootReady === "function") window.__obsigateBootReady();
|
|
||||||
}
|
|
||||||
// Show version in header — always visible, fallback before the async fetch
|
// Show version in header — always visible, fallback before the async fetch
|
||||||
populateVersions();
|
populateVersions();
|
||||||
fetch('/api/health')
|
fetch('/api/health')
|
||||||
|
|||||||
@@ -25,22 +25,6 @@
|
|||||||
comportements de la SPA et ne sont pas rejoués ici.
|
comportements de la SPA et ne sont pas rejoués ici.
|
||||||
Sidebar statique (navigation de l'app) : les vaults, tags et
|
Sidebar statique (navigation de l'app) : les vaults, tags et
|
||||||
récents dynamiques vivent dans la SPA, pas sur cette page. -->
|
récents dynamiques vivent dans la SPA, pas sur cette page. -->
|
||||||
Thème (défaut 4) : le thème ObsiGate n'est PAS une préférence
|
|
||||||
serveur — il vit dans localStorage, lu par la SPA au démarrage
|
|
||||||
(frontend/js/ui.js initTheme : mode ; frontend/js/sync.js
|
|
||||||
init() -> themes.js initThemes : clé + mode). Étape 1 ci-dessous
|
|
||||||
(script synchrone en tête, avant les feuilles) : pose
|
|
||||||
data-theme = mode persisté (obsigate-theme-mode, repli
|
|
||||||
obsigate-theme historique quand il valait déjà un mode, sinon
|
|
||||||
'dark', le défaut SPA) pour que la première peinture soit déjà
|
|
||||||
dans le bon mode clair/sombre. Étape 2 en fin de page (module
|
|
||||||
différé) : initThemes() de themes.js applique la palette
|
|
||||||
complète — 16 thèmes intégrés, modes high-contrast/sepia
|
|
||||||
générés, thèmes personnalisés — via variables inline sur :root,
|
|
||||||
exactement comme la SPA. data-theme="dark" sur <html> n'est
|
|
||||||
plus que le repli sans JS. Aucun endpoint serveur, aucune
|
|
||||||
police externe ajoutée (la CSP stricte de /recherche et le
|
|
||||||
repli typographique système sont inchangés).
|
|
||||||
<script>
|
<script>
|
||||||
/* Thème ObsiGate, étape 1 (synchrone, avant les feuilles) : poser
|
/* Thème ObsiGate, étape 1 (synchrone, avant les feuilles) : poser
|
||||||
data-theme = mode persisté AVANT la première peinture, sinon la
|
data-theme = mode persisté AVANT la première peinture, sinon la
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsigate",
|
"name": "obsigate",
|
||||||
"version": "2.82.2",
|
"version": "2.82.4",
|
||||||
"description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.",
|
"description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.",
|
||||||
"main": "patch.js",
|
"main": "patch.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def test_origin_of():
|
|||||||
|
|
||||||
|
|
||||||
def test_sister_origins_dev():
|
def test_sister_origins_dev():
|
||||||
# Correctif 2026-10-28 : 8 origines officielles, prod ET dev,
|
# Correctif 2026-10-28 : 10 origines officielles (8 sœurs + 2 agent), prod ET dev,
|
||||||
# quelle que soit la résolution.
|
# quelle que soit la résolution.
|
||||||
assert sister_origins(RESOLVED_DEV) == {
|
assert sister_origins(RESOLVED_DEV) == {
|
||||||
"https://og.dracodev.net",
|
"https://og.dracodev.net",
|
||||||
@@ -49,9 +49,24 @@ def test_sister_origins_dev():
|
|||||||
"https://flowdeck-dev.dracodev.net",
|
"https://flowdeck-dev.dracodev.net",
|
||||||
"https://tiktak-dev.dracodev.net",
|
"https://tiktak-dev.dracodev.net",
|
||||||
"https://kanbante-dev.dracodev.net",
|
"https://kanbante-dev.dracodev.net",
|
||||||
|
"https://agent.dracodev.net",
|
||||||
|
"https://agent-dev.dracodev.net",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_cors_headers_agent_console_autorise():
|
||||||
|
# Fédération depuis la console (2026-10-28) : les origines agent
|
||||||
|
# prod ET dev passent, avec credentials ; un tiers reste refusé.
|
||||||
|
for origine in ("https://agent.dracodev.net",
|
||||||
|
"https://agent-dev.dracodev.net"):
|
||||||
|
headers = cors_headers_for(origine, RESOLVED_DEV)
|
||||||
|
assert headers is not None
|
||||||
|
assert headers["Access-Control-Allow-Origin"] == origine
|
||||||
|
assert headers["Access-Control-Allow-Credentials"] == "true"
|
||||||
|
assert cors_headers_for("https://agent.dracodev.net.evil.example",
|
||||||
|
RESOLVED_DEV) is None
|
||||||
|
|
||||||
def test_sister_origins_ajoute_env_de_la_requete():
|
def test_sister_origins_ajoute_env_de_la_requete():
|
||||||
# Correctif 2026-10-28 : configuration muette, quel que soit
|
# Correctif 2026-10-28 : configuration muette, quel que soit
|
||||||
# l'hôte de la requête, prod ET dev passent — l'hôte ne
|
# l'hôte de la requête, prod ET dev passent — l'hôte ne
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
"""Tests — page /recherche sans texte parasite dans <head>.
|
||||||
|
|
||||||
|
Régression du texte de chantier resté hors commentaire dans
|
||||||
|
``frontend/recherche.html`` : le commentaire de tête se fermait, puis
|
||||||
|
un paragraphe de description du mécanisme de thème suivait en texte
|
||||||
|
brut dans ``<head>`` — le navigateur le rendait tout en haut de la
|
||||||
|
page, au-dessus de la barre header. Le correctif supprime ce texte
|
||||||
|
du rendu (il n'est pas remis en commentaire : un grep doit donner
|
||||||
|
0 occurrence dans les gabarits) sans toucher au mécanisme lui-même,
|
||||||
|
déjà documenté par les commentaires des scripts étape 1 / étape 2.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
FRONTEND = ROOT / "frontend"
|
||||||
|
|
||||||
|
|
||||||
|
def _html() -> str:
|
||||||
|
return (FRONTEND / "recherche.html").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def test_pas_de_texte_brut_dans_head():
|
||||||
|
"""Entre les balises et les scripts, <head> ne contient aucun
|
||||||
|
texte rendu : tout descriptif vit dans un commentaire ou dans un
|
||||||
|
commentaire de script, jamais en texte brut."""
|
||||||
|
head = _html().split("</head>")[0]
|
||||||
|
sans_commentaires = re.sub(r"<!--.*?-->", "", head, flags=re.DOTALL)
|
||||||
|
sans_scripts = re.sub(
|
||||||
|
r"<script.*?</script>", "", sans_commentaires, flags=re.DOTALL
|
||||||
|
)
|
||||||
|
sans_titre = re.sub(
|
||||||
|
r"<title>.*?</title>", "", sans_scripts, flags=re.DOTALL
|
||||||
|
)
|
||||||
|
texte = re.sub(r"<[^>]+>", "", sans_titre).strip()
|
||||||
|
assert texte == "", f"texte brut rendu dans <head> : {texte[:200]!r}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_mecanisme_theme_intact():
|
||||||
|
"""Le retrait du texte ne touche ni l'étape 1 (script synchrone
|
||||||
|
avant les feuilles) ni l'étape 2 (initThemes différé)."""
|
||||||
|
html = _html()
|
||||||
|
assert html.index("<script>") < html.index('<link rel="stylesheet"')
|
||||||
|
assert "obsigate-theme-mode" in html
|
||||||
|
assert "import('/static/js/themes.js')" in html
|
||||||
|
assert "initThemes" in html
|
||||||
|
assert '<html lang="fr" data-theme="dark">' in html
|
||||||
|
assert '<script src="/static/js/recherche.js"></script>' in html
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
"""Tests — démarrage ObsiGate sans écran de chargement plein écran.
|
||||||
|
|
||||||
|
Le splash (``#boot-splash`` dans ``frontend/index.html`` : icône
|
||||||
|
livre, titre, spinner, « Démarrage… ») masquait tout le reste de la
|
||||||
|
page via ``body:not(.app-booted) > * { display: none !important }``
|
||||||
|
jusqu'au signal de ``frontend/js/app.js`` (ou un repli à 10 s).
|
||||||
|
Les autres applications (TikTak, Kanbante, Flowdeck, console Spark)
|
||||||
|
n'affichent aucun splash de ce type au démarrage : le contenu y
|
||||||
|
apparaît dès qu'il est prêt. Le retrait supprime donc l'écran, son
|
||||||
|
CSS de masquage, son script de pilotage et le signal côté ``app.js``,
|
||||||
|
sans toucher aux indicateurs internes aux vues ni à l'écran de
|
||||||
|
connexion.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
FRONTEND = ROOT / "frontend"
|
||||||
|
|
||||||
|
MARQUEURS_SPLASH = (
|
||||||
|
"boot-splash",
|
||||||
|
"app-booted",
|
||||||
|
"__OBSIGATE_BOOTED",
|
||||||
|
"__obsigateBootReady",
|
||||||
|
"__setBootStatus",
|
||||||
|
"bs-card",
|
||||||
|
"bs-logo",
|
||||||
|
"bs-title",
|
||||||
|
"bs-spinner",
|
||||||
|
"bs-status",
|
||||||
|
"bs-spin",
|
||||||
|
"Démarrage…",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _index() -> str:
|
||||||
|
return (FRONTEND / "index.html").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def _app_js() -> str:
|
||||||
|
return (FRONTEND / "js" / "app.js").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def test_aucun_marqueur_splash():
|
||||||
|
index = _index()
|
||||||
|
app_js = _app_js()
|
||||||
|
for marqueur in MARQUEURS_SPLASH:
|
||||||
|
assert marqueur not in index, f"{marqueur} encore dans index.html"
|
||||||
|
assert marqueur not in app_js, f"{marqueur} encore dans js/app.js"
|
||||||
|
|
||||||
|
|
||||||
|
def test_pas_de_masquage_global_au_demarrage():
|
||||||
|
"""Aucune règle ne cache tout le <body> en attendant un signal :
|
||||||
|
sans elle, un chargement lent ne peut plus donner un écran blanc
|
||||||
|
bloqué."""
|
||||||
|
index = _index()
|
||||||
|
assert "body:not(.app-booted)" not in index
|
||||||
|
assert "display: none !important" not in index
|
||||||
|
|
||||||
|
|
||||||
|
def test_boot_js_conserve_et_indicateurs_internes_intacts():
|
||||||
|
"""Le démarrage SPA reste branché ; seuls le splash et son signal
|
||||||
|
disparaissent. L'écran de connexion et la barre de progression
|
||||||
|
de recherche (indicateurs internes aux vues) ne sont PAS visés."""
|
||||||
|
app_js = _app_js()
|
||||||
|
assert 'document.addEventListener("DOMContentLoaded"' in app_js
|
||||||
|
assert "await init();" in app_js
|
||||||
|
assert "Sync.init();" in app_js
|
||||||
|
assert "populateVersions" in app_js
|
||||||
|
index = _index()
|
||||||
|
assert 'id="login-screen"' in index
|
||||||
|
assert 'id="search-progress-bar"' in index
|
||||||
|
assert 'id="app"' in index
|
||||||
Reference in New Issue
Block a user