From 20c9244ee4434ef89adbfcdcc8f36e70414e3ca3 Mon Sep 17 00:00:00 2001 From: helbertm Date: Thu, 23 Jul 2026 10:59:31 -0300 Subject: [PATCH 1/4] feat(schema): keep quick guide open in empty state --- index.html | 32 ++++++++++++++++++-- scripts/validate-runtime-smoke.mjs | 37 ++++++++++++++++++++++++ src/capabilities/01-dom-layout-schema.js | 2 ++ src/index.template.html | 4 +-- src/ui/10-schema.js | 26 ++++++++++++++++- 5 files changed, 96 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index 3244767..2335f32 100644 --- a/index.html +++ b/index.html @@ -5302,8 +5302,8 @@

Schema

Nenhum arquivo carregado.
-
- Guia rápido +
+ Guia rápido
Use F4 para ocultar/mostrar este painel.
Clique em uma tabela com Cmd/Ctrl para inserir o nome.
@@ -28232,6 +28232,8 @@

Modo offline indisponível

schemaOtherMenu: () => schemaOtherMenu, schemaPanel: () => schemaPanel, schemaPanelController: () => schemaPanelController, + schemaQuickGuide: () => schemaQuickGuide, + schemaQuickGuideSummary: () => schemaQuickGuideSummary, schemaSearch: () => schemaSearch, schemaToggleRail: () => schemaToggleRail, schemaTypeAllBtn: () => schemaTypeAllBtn, @@ -28286,6 +28288,7 @@

Modo offline indisponível

shortcutsHelpBtn: () => shortcutsHelpBtn, shortcutsHelpModal: () => shortcutsHelpModal, shouldExecuteSqlAfterFileOpen: () => shouldExecuteSqlAfterFileOpen, + shouldKeepSchemaQuickGuideOpen: () => shouldKeepSchemaQuickGuideOpen, showSqlMapEdgeTooltip: () => showSqlMapEdgeTooltip, showToast: () => showToast, shuffleList: () => shuffleList, @@ -28374,6 +28377,7 @@

Modo offline indisponível

summarizeSqlMapCompatibilityReason: () => summarizeSqlMapCompatibilityReason, switchSqlTab: () => switchSqlTab2, syncActiveResultToTab: () => syncActiveResultToTab, + syncSchemaQuickGuide: () => syncSchemaQuickGuide, syncSqlMapFieldDragUi: () => syncSqlMapFieldDragUi, syncTabNamePresetSelectValue: () => syncTabNamePresetSelectValue, t: () => t, @@ -30937,6 +30941,8 @@

Modo offline indisponível

var resultHScroll = document.getElementById("resultHScroll"); var resultHScrollRange = document.getElementById("resultHScrollRange"); var schemaList = document.getElementById("schemaList"); + var schemaQuickGuide = document.getElementById("schemaQuickGuide"); + var schemaQuickGuideSummary = document.getElementById("schemaQuickGuideSummary"); var schemaSearch = document.getElementById("schemaSearch"); var schemaTypeFilters = document.getElementById("schemaTypeFilters"); var schemaTypeAllBtn = document.getElementById("schemaTypeAllBtn"); @@ -33986,6 +33992,14 @@

Modo offline indisponível

} return count; } + function shouldKeepSchemaQuickGuideOpen() { + return !getActiveDatabase() && Object.keys(getSchemaObjects()).length === 0; + } + function syncSchemaQuickGuide() { + if (schemaQuickGuide && shouldKeepSchemaQuickGuideOpen()) { + schemaQuickGuide.open = true; + } + } var schemaController = { load() { const activeDb = getActiveDatabase(); @@ -34063,6 +34077,7 @@

Modo offline indisponível

}).join(""); }, render() { + syncSchemaQuickGuide(); const filter = schemaSearch.value.trim().toLowerCase(); const schemaFilterState = getSchemaFilterState(); const entries = Object.entries(getSchemaObjects()).filter(([table, meta]) => { @@ -34127,6 +34142,18 @@

Modo offline indisponível

schemaController.updateSearchClearButton(); } function bindSchemaUi() { + if (schemaQuickGuideSummary) { + schemaQuickGuideSummary.addEventListener("click", (event) => { + if (!shouldKeepSchemaQuickGuideOpen()) return; + event.preventDefault(); + schemaQuickGuide.open = true; + }); + } + if (schemaQuickGuide) { + schemaQuickGuide.addEventListener("toggle", () => { + syncSchemaQuickGuide(); + }); + } schemaSearch.addEventListener("input", () => { updateSchemaSearchClearButton(); renderSchema(); @@ -34214,6 +34241,7 @@

Modo offline indisponível

loadSchemaFiltersPreference(); schemaController.renderTypeFilters(); updateSchemaSearchClearButton(); + syncSchemaQuickGuide(); } // src/capabilities/10-sql-execution.js diff --git a/scripts/validate-runtime-smoke.mjs b/scripts/validate-runtime-smoke.mjs index 9ac04f1..0d2f1f4 100644 --- a/scripts/validate-runtime-smoke.mjs +++ b/scripts/validate-runtime-smoke.mjs @@ -200,7 +200,9 @@ async function runExecutionGridSuite(runtime, context) { "openExportModal", "isCurrentDbDirty" ]); + assertQuickGuideEmptyState(runtime); await openRuntimeDatabase(runtime, context); + assertQuickGuideAvailableAfterDatabaseLoad(runtime); assertSchemaFlows(runtime, context); await assertSqlExecutionMultiStatementFlows(runtime, context); await assertSqlExecutionMixedStatementFlows(runtime, context); @@ -2127,6 +2129,41 @@ async function assertSqlExecutionMixedStatementFlows(runtime, context) { ); } +function assertQuickGuideEmptyState(runtime) { + const quickGuide = runtime.elementsById.get("schemaQuickGuide"); + const quickGuideSummary = runtime.elementsById.get("schemaQuickGuideSummary"); + + assert( + quickGuide?.open === true, + "Runtime smoke expected the Quick guide to initialize expanded without an active database or schema." + ); + + quickGuide.open = false; + const toggleAllowed = quickGuideSummary.dispatchEvent({ + type: "click", + target: quickGuideSummary + }); + assert( + toggleAllowed === false && quickGuide.open === true, + "Runtime smoke expected the Quick guide to remain expanded throughout the true empty state." + ); +} + +function assertQuickGuideAvailableAfterDatabaseLoad(runtime) { + const quickGuide = runtime.elementsById.get("schemaQuickGuide"); + const quickGuideSummary = runtime.elementsById.get("schemaQuickGuideSummary"); + + quickGuide.open = false; + const toggleAllowed = quickGuideSummary.dispatchEvent({ + type: "click", + target: quickGuideSummary + }); + assert( + toggleAllowed === true && quickGuide.open === false, + "Runtime smoke expected the Quick guide disclosure to become user-controlled after a database loads." + ); +} + function assertSchemaFlows(runtime, context) { const schemaList = runtime.elementsById.get("schemaList"); const schemaSearch = runtime.elementsById.get("schemaSearch"); diff --git a/src/capabilities/01-dom-layout-schema.js b/src/capabilities/01-dom-layout-schema.js index 17862c6..e54ed8f 100644 --- a/src/capabilities/01-dom-layout-schema.js +++ b/src/capabilities/01-dom-layout-schema.js @@ -2,6 +2,8 @@ export const tableWrap = document.getElementById("tableWrap"); export const resultHScroll = document.getElementById("resultHScroll"); export const resultHScrollRange = document.getElementById("resultHScrollRange"); export const schemaList = document.getElementById("schemaList"); +export const schemaQuickGuide = document.getElementById("schemaQuickGuide"); +export const schemaQuickGuideSummary = document.getElementById("schemaQuickGuideSummary"); export const schemaSearch = document.getElementById("schemaSearch"); export const schemaTypeFilters = document.getElementById("schemaTypeFilters"); export const schemaTypeAllBtn = document.getElementById("schemaTypeAllBtn"); diff --git a/src/index.template.html b/src/index.template.html index 21977b6..ea1485b 100644 --- a/src/index.template.html +++ b/src/index.template.html @@ -114,8 +114,8 @@

Schema

Nenhum arquivo carregado.
-
- Guia rápido +
+ Guia rápido
Use F4 para ocultar/mostrar este painel.
Clique em uma tabela com Cmd/Ctrl para inserir o nome.
diff --git a/src/ui/10-schema.js b/src/ui/10-schema.js index af6fd2b..300fab9 100644 --- a/src/ui/10-schema.js +++ b/src/ui/10-schema.js @@ -1,4 +1,4 @@ -import { clearSchemaFiltersBtn, clearSchemaSearchBtn, schemaList, schemaOtherMenu, schemaSearch, schemaTypeAllBtn, schemaTypeFilters, schemaTypeOtherBtn, schemaTypeTableBtn, schemaTypeViewBtn } from "../capabilities/01-dom-layout-schema.js"; +import { clearSchemaFiltersBtn, clearSchemaSearchBtn, schemaList, schemaOtherMenu, schemaQuickGuide, schemaQuickGuideSummary, schemaSearch, schemaTypeAllBtn, schemaTypeFilters, schemaTypeOtherBtn, schemaTypeTableBtn, schemaTypeViewBtn } from "../capabilities/01-dom-layout-schema.js"; import { formatNumber, t } from "../capabilities/03-localization.js"; import { sqlEscapeIdent } from "../core/07-sql-escaping.js"; import { getActiveDatabase } from "../capabilities/07-database-runtime.js"; @@ -75,6 +75,16 @@ export function normalizeSchemaType(type) { return count; } + export function shouldKeepSchemaQuickGuideOpen() { + return !getActiveDatabase() && Object.keys(getSchemaObjects()).length === 0; + } + + export function syncSchemaQuickGuide() { + if (schemaQuickGuide && shouldKeepSchemaQuickGuideOpen()) { + schemaQuickGuide.open = true; + } + } + export const schemaController = { load() { const activeDb = getActiveDatabase(); @@ -159,6 +169,7 @@ export function normalizeSchemaType(type) { }).join(""); }, render() { + syncSchemaQuickGuide(); const filter = schemaSearch.value.trim().toLowerCase(); const schemaFilterState = getSchemaFilterState(); const entries = Object.entries(getSchemaObjects()).filter(([table, meta]) => { @@ -232,6 +243,18 @@ export function normalizeSchemaType(type) { } export function bindSchemaUi() { + if (schemaQuickGuideSummary) { + schemaQuickGuideSummary.addEventListener("click", (event) => { + if (!shouldKeepSchemaQuickGuideOpen()) return; + event.preventDefault(); + schemaQuickGuide.open = true; + }); + } + if (schemaQuickGuide) { + schemaQuickGuide.addEventListener("toggle", () => { + syncSchemaQuickGuide(); + }); + } schemaSearch.addEventListener("input", () => { updateSchemaSearchClearButton(); renderSchema(); @@ -321,4 +344,5 @@ export function normalizeSchemaType(type) { loadSchemaFiltersPreference(); schemaController.renderTypeFilters(); updateSchemaSearchClearButton(); + syncSchemaQuickGuide(); } From 893fa6926c136bf7a3c7fc5f29a1800f7953d42b Mon Sep 17 00:00:00 2001 From: helbertm Date: Thu, 23 Jul 2026 11:02:22 -0300 Subject: [PATCH 2/4] docs(i18n): add local settings backup guidance --- index.html | 9 +++++---- scripts/validate-runtime-smoke.mjs | 20 +++++++++++++++++++ src/capabilities/03a-localization-messages.js | 4 ++-- src/index.template.html | 5 +++-- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/index.html b/index.html index 2335f32..c2ec5ae 100644 --- a/index.html +++ b/index.html @@ -5321,6 +5321,7 @@

Schema

Arraste a borda direita do cabeçalho para aumentar/diminuir. Duplo clique na borda restaura a largura automática.
Ctrl/Cmd+clique no cabeçalho congela da primeira coluna até a coluna clicada.
Arraste o cabeçalho da coluna para mudar sua posição. +

Backup local: as configurações e os dados salvos da área de trabalho ficam apenas neste navegador. Exporte periodicamente um backup JSON em Configurações para evitar perdas ou continuar em outro navegador. Ele pode conter SQL; revise-o antes de compartilhar.
@@ -5913,8 +5914,8 @@

4. Resultados

Ordenação multi-coluna, paginação, seleção de linhas, exportação CSV/JSON e cabeçalho sempre visível.
-

5. Persistência

-
Sessão, histórico, favoritos e preferências ficam no navegador (localStorage/IndexedDB).
+

5. Dados locais e backups

+
Dados da sessão, histórico, favoritas e preferências ficam armazenados apenas no perfil atual deste navegador. Exporte-os periodicamente em Configurações para evitar perdas. Importe esse backup JSON para continuar em outro navegador. Limpar os dados do site, usar uma janela privativa ou mudar de perfil pode tornar os dados locais indisponíveis. Backups podem conter textos SQL, identificadores, histórico, favoritas e conteúdo das abas abertas; revise-os antes de compartilhar.

6. Modo offline

@@ -30111,7 +30112,7 @@

Modo offline indisponível

["editor.ariaLabel", "SQL statement editor", "Editor de instru\xE7\xF5es SQL", "Editor de sentencias SQL"], ["editor.findNext", "Next occurrence", "Pr\xF3xima ocorr\xEAncia", "Coincidencia siguiente"], ["history.quickLabel", "Quick query history", "Hist\xF3rico r\xE1pido de consultas", "Historial r\xE1pido de consultas"], - ["schema.quickGuideHtml", 'Use F4 to show or hide this panel.
Click a table while holding Cmd/Ctrl to insert its name. Click a column to insert table.column.

Autocomplete: table and column suggestions follow the active SQL context. Ctrl/Cmd+Space opens suggestions when the context is valid.

Execution: long queries run in a worker so the interface remains responsive. Cancel stops the worker.

Results: use Shift+click for multi-column sorting, drag column edges to resize, and Ctrl/Cmd+click to freeze columns.', 'Use F4 para mostrar ou ocultar este painel.
Clique em uma tabela com Cmd/Ctrl para inserir o nome. Clique em uma coluna para inserir tabela.coluna.

Autocompletar: as sugest\xF5es de tabelas e colunas seguem o contexto SQL ativo. Ctrl/Cmd+Espa\xE7o abre as sugest\xF5es quando o contexto \xE9 v\xE1lido.

Execu\xE7\xE3o: consultas longas rodam em um worker para manter a interface responsiva. Cancelar encerra o worker.

Resultados: use Shift+clique para ordenar por v\xE1rias colunas, arraste as bordas para redimensionar e use Ctrl/Cmd+clique para congelar colunas.', 'Use F4 para mostrar u ocultar este panel.
Haga clic en una tabla con Cmd/Ctrl para insertar su nombre. Haga clic en una columna para insertar tabla.columna.

Autocompletar: las sugerencias de tablas y columnas siguen el contexto SQL activo. Ctrl/Cmd+Espacio abre las sugerencias cuando el contexto es v\xE1lido.

Ejecuci\xF3n: las consultas largas se ejecutan en un worker para mantener la interfaz receptiva. Cancelar detiene el worker.

Resultados: use May\xFAs+clic para ordenar por varias columnas, arrastre los bordes para cambiar el tama\xF1o y use Ctrl/Cmd+clic para inmovilizar columnas.'], + ["schema.quickGuideHtml", 'Use F4 to show or hide this panel.
Click a table while holding Cmd/Ctrl to insert its name. Click a column to insert table.column.

Autocomplete: table and column suggestions follow the active SQL context. Ctrl/Cmd+Space opens suggestions when the context is valid.

Execution: long queries run in a worker so the interface remains responsive. Cancel stops the worker.

Results: use Shift+click for multi-column sorting, drag column edges to resize, and Ctrl/Cmd+click to freeze columns.

Local backup: settings and saved workspace data stay only in this browser. Periodically export a JSON backup from Settings to avoid loss or continue in another browser. It may contain SQL; review it before sharing.', 'Use F4 para mostrar ou ocultar este painel.
Clique em uma tabela com Cmd/Ctrl para inserir o nome. Clique em uma coluna para inserir tabela.coluna.

Autocompletar: as sugest\xF5es de tabelas e colunas seguem o contexto SQL ativo. Ctrl/Cmd+Espa\xE7o abre as sugest\xF5es quando o contexto \xE9 v\xE1lido.

Execu\xE7\xE3o: consultas longas rodam em um worker para manter a interface responsiva. Cancelar encerra o worker.

Resultados: use Shift+clique para ordenar por v\xE1rias colunas, arraste as bordas para redimensionar e use Ctrl/Cmd+clique para congelar colunas.

Backup local: as configura\xE7\xF5es e os dados salvos da \xE1rea de trabalho ficam apenas neste navegador. Exporte periodicamente um backup JSON em Configura\xE7\xF5es para evitar perdas ou continuar em outro navegador. Ele pode conter SQL; revise-o antes de compartilhar.', 'Use F4 para mostrar u ocultar este panel.
Haga clic en una tabla con Cmd/Ctrl para insertar su nombre. Haga clic en una columna para insertar tabla.columna.

Autocompletar: las sugerencias de tablas y columnas siguen el contexto SQL activo. Ctrl/Cmd+Espacio abre las sugerencias cuando el contexto es v\xE1lido.

Ejecuci\xF3n: las consultas largas se ejecutan en un worker para mantener la interfaz receptiva. Cancelar detiene el worker.

Resultados: use May\xFAs+clic para ordenar por varias columnas, arrastre los bordes para cambiar el tama\xF1o y use Ctrl/Cmd+clic para inmovilizar columnas.

Copia local: la configuraci\xF3n y los datos guardados del \xE1rea de trabajo permanecen solo en este navegador. Exporte peri\xF3dicamente una copia JSON desde Configuraci\xF3n para evitar p\xE9rdidas o continuar en otro navegador. Puede contener SQL; rev\xEDsela antes de compartirla.'], ["sqlMap.description", "Interactive ER diagram with SELECT and JOIN generation from declared foreign keys and session virtual relationships.", "DER interativo com gera\xE7\xE3o de SELECT e JOINs a partir de FKs declaradas e relacionamentos virtuais da sess\xE3o.", "Diagrama ER interactivo con generaci\xF3n de SELECT y JOIN a partir de claves for\xE1neas declaradas y relaciones virtuales de la sesi\xF3n."], ["sqlMap.selection", "Selection", "Sele\xE7\xE3o", "Selecci\xF3n"], ["sqlMap.virtualHint", "Drag one field onto another to create a virtual relationship. Hover over a dotted line to inspect its technical details.", "Arraste um campo sobre outro para criar um relacionamento virtual. Passe o mouse sobre a linha pontilhada para ver os detalhes t\xE9cnicos.", "Arrastre un campo sobre otro para crear una relaci\xF3n virtual. Pase el puntero sobre una l\xEDnea de puntos para ver sus detalles t\xE9cnicos."], @@ -30134,7 +30135,7 @@

Modo offline indisponível

["shortcuts.quickHistory", "Quick history", "Hist\xF3rico r\xE1pido", "Historial r\xE1pido"], ["help.title", "hSQLite Editor help", "Ajuda do hSQLite Editor", "Ayuda de hSQLite Editor"], ["help.intro", "A local-first SQLite analysis tool for support, teaching, and technical investigation.", "Uma ferramenta local-first para an\xE1lise de SQLite em suporte, ensino e investiga\xE7\xE3o t\xE9cnica.", "Una herramienta local-first para analizar SQLite en soporte, docencia e investigaci\xF3n t\xE9cnica."], - ["help.gridHtml", '

1. Getting started

Open a SQLite file, write SQL, and press F5.

2. SQL workspace

Use independent tabs, history, favorites, SQL files, and keyboard shortcuts.

3. Schema

Filter objects by name and type. Select columns to insert qualified identifiers.

4. Results

Sort, paginate, select, and export rows as CSV or JSON.

5. Local data

Session, history, favorites, and preferences stay in this browser.

6. Offline mode

The official standalone HTML works directly from file://.

7. Issues

Use Report a bug to open the project issue tracker.
', '

1. Primeiros passos

Abra um arquivo SQLite, escreva SQL e pressione F5.

2. \xC1rea SQL

Use abas independentes, hist\xF3rico, favoritas, arquivos SQL e atalhos de teclado.

3. Schema

Filtre objetos por nome e tipo. Selecione colunas para inserir identificadores qualificados.

4. Resultados

Ordene, pagine, selecione e exporte registros como CSV ou JSON.

5. Dados locais

Sess\xE3o, hist\xF3rico, favoritas e prefer\xEAncias permanecem neste navegador.

6. Modo offline

O HTML standalone oficial funciona diretamente em file://.

7. Problemas

Use Reportar erro para abrir o rastreador de issues do projeto.
', '

1. Primeros pasos

Abra un archivo SQLite, escriba SQL y pulse F5.

2. \xC1rea SQL

Use pesta\xF1as independientes, historial, favoritas, archivos SQL y atajos de teclado.

3. Esquema

Filtre objetos por nombre y tipo. Seleccione columnas para insertar identificadores cualificados.

4. Resultados

Ordene, pagine, seleccione y exporte filas como CSV o JSON.

5. Datos locales

La sesi\xF3n, el historial, las favoritas y las preferencias permanecen en este navegador.

6. Modo sin conexi\xF3n

El HTML aut\xF3nomo oficial funciona directamente desde file://.

7. Problemas

Use Informar de un error para abrir el gestor de incidencias del proyecto.
'], + ["help.gridHtml", '

1. Getting started

Open a SQLite file, write SQL, and press F5.

2. SQL workspace

Use independent tabs, history, favorites, SQL files, and keyboard shortcuts.

3. Schema

Filter objects by name and type. Select columns to insert qualified identifiers.

4. Results

Sort, paginate, select, and export rows as CSV or JSON.

5. Local data and backups

Session data, history, favorites, and preferences are stored only in the current browser profile. Export them periodically from Settings to prevent loss. Import that JSON backup to continue in another browser. Clearing site data, using a private window, or changing browser profiles can make local data unavailable. Backups may contain SQL text, identifiers, history, favorites, and open-tab content; review them before sharing.

6. Offline mode

The official standalone HTML works directly from file://.

7. Issues

Use Report a bug to open the project issue tracker.
', '

1. Primeiros passos

Abra um arquivo SQLite, escreva SQL e pressione F5.

2. \xC1rea SQL

Use abas independentes, hist\xF3rico, favoritas, arquivos SQL e atalhos de teclado.

3. Schema

Filtre objetos por nome e tipo. Selecione colunas para inserir identificadores qualificados.

4. Resultados

Ordene, pagine, selecione e exporte registros como CSV ou JSON.

5. Dados locais e backups

Dados da sess\xE3o, hist\xF3rico, favoritas e prefer\xEAncias ficam armazenados apenas no perfil atual deste navegador. Exporte-os periodicamente em Configura\xE7\xF5es para evitar perdas. Importe esse backup JSON para continuar em outro navegador. Limpar os dados do site, usar uma janela privativa ou mudar de perfil pode tornar os dados locais indispon\xEDveis. Backups podem conter textos SQL, identificadores, hist\xF3rico, favoritas e conte\xFAdo das abas abertas; revise-os antes de compartilhar.

6. Modo offline

O HTML standalone oficial funciona diretamente em file://.

7. Problemas

Use Reportar erro para abrir o rastreador de issues do projeto.
', '

1. Primeros pasos

Abra un archivo SQLite, escriba SQL y pulse F5.

2. \xC1rea SQL

Use pesta\xF1as independientes, historial, favoritas, archivos SQL y atajos de teclado.

3. Esquema

Filtre objetos por nombre y tipo. Seleccione columnas para insertar identificadores cualificados.

4. Resultados

Ordene, pagine, seleccione y exporte filas como CSV o JSON.

5. Datos locales y copias de seguridad

Los datos de la sesi\xF3n, el historial, las favoritas y las preferencias se almacenan solo en el perfil actual de este navegador. Exp\xF3rtelos peri\xF3dicamente desde Configuraci\xF3n para evitar p\xE9rdidas. Importe esa copia JSON para continuar en otro navegador. Borrar los datos del sitio, usar una ventana privada o cambiar de perfil puede hacer que los datos locales no est\xE9n disponibles. Las copias pueden contener texto SQL, identificadores, historial, favoritas y contenido de las pesta\xF1as abiertas; rev\xEDselas antes de compartirlas.

6. Modo sin conexi\xF3n

El HTML aut\xF3nomo oficial funciona directamente desde file://.

7. Problemas

Use Informar de un error para abrir el gestor de incidencias del proyecto.
'], ["offline.description", "This artifact is expected to work entirely offline. This screen means the file is incomplete or corrupted.", "Este artefato deve funcionar totalmente offline. Esta tela indica que o arquivo est\xE1 incompleto ou corrompido.", "Este artefacto debe funcionar totalmente sin conexi\xF3n. Esta pantalla indica que el archivo est\xE1 incompleto o da\xF1ado."], ["offline.guideHtml", "Expected: the standalone HTML contains every required resource.

Verify:
  • Build the artifact again with the repository build command.
  • Replace old HTML copies with a fresh artifact from the same release.
  • If the file was modified manually, restore the official generated artifact.
Then open the local file again.", "Esperado: o HTML standalone cont\xE9m todos os recursos necess\xE1rios.

Verifique:
  • Gere o artefato novamente com o comando de build do reposit\xF3rio.
  • Substitua c\xF3pias antigas do HTML por um artefato novo do mesmo release.
  • Se o arquivo foi modificado manualmente, restaure o artefato oficial gerado.
Depois abra o arquivo local novamente.", "Esperado: el HTML aut\xF3nomo contiene todos los recursos necesarios.

Compruebe:
  • Genere de nuevo el artefacto con el comando de compilaci\xF3n del repositorio.
  • Sustituya las copias antiguas del HTML por un artefacto nuevo de la misma versi\xF3n.
  • Si el archivo se modific\xF3 manualmente, restaure el artefacto oficial generado.
Despu\xE9s vuelva a abrir el archivo local."], ["tabs.renameLabel", "Rename tab", "Renomear aba", "Renombrar pesta\xF1a"], diff --git a/scripts/validate-runtime-smoke.mjs b/scripts/validate-runtime-smoke.mjs index 0d2f1f4..7da9be1 100644 --- a/scripts/validate-runtime-smoke.mjs +++ b/scripts/validate-runtime-smoke.mjs @@ -298,6 +298,16 @@ async function assertGeneratedDatabaseReplacementFlows(runtime, context) { function assertAdvancedLocalizationFlows(runtime, context) { const observedHelpTitles = new Set(); + const localizedBackupLabels = { + "en-US": "Local backup", + "pt-BR": "Backup local", + "es-ES": "Copia local" + }; + const localizedHelpHeadings = { + "en-US": "Local data and backups", + "pt-BR": "Dados locais e backups", + "es-ES": "Datos locales y copias de seguridad" + }; for (const locale of ["en-US", "pt-BR", "es-ES"]) { context.setLocale(locale, { persist: false }); assert(runtime.document.documentElement.lang === locale, `Runtime smoke expected document language ${locale}.`); @@ -308,6 +318,16 @@ function assertAdvancedLocalizationFlows(runtime, context) { const helpTitle = localizedProbe.textContent; assert(helpTitle === context.t("help.title"), `Runtime smoke expected explicit data-i18n rendering for ${locale}.`); observedHelpTitles.add(helpTitle); + assert( + context.t("schema.quickGuideHtml").includes(localizedBackupLabels[locale]) && + context.t("schema.quickGuideHtml").includes("JSON"), + `Runtime smoke expected concise local-backup guidance in the Quick guide for ${locale}.` + ); + assert( + context.t("help.gridHtml").includes(localizedHelpHeadings[locale]) && + context.t("help.gridHtml").includes("JSON"), + `Runtime smoke expected detailed local-backup guidance in Help for ${locale}.` + ); const textStrategies = context.getTablePopulationStrategyOptions({ name: "notes", type: "TEXT", diff --git a/src/capabilities/03a-localization-messages.js b/src/capabilities/03a-localization-messages.js index 34aa7ee..3df6120 100644 --- a/src/capabilities/03a-localization-messages.js +++ b/src/capabilities/03a-localization-messages.js @@ -136,7 +136,7 @@ export const I18N_MESSAGE_ROWS = Object.freeze([ , ["editor.ariaLabel", "SQL statement editor", "Editor de instruções SQL", "Editor de sentencias SQL"] , ["editor.findNext", "Next occurrence", "Próxima ocorrência", "Coincidencia siguiente"] , ["history.quickLabel", "Quick query history", "Histórico rápido de consultas", "Historial rápido de consultas"] - , ["schema.quickGuideHtml", "Use F4 to show or hide this panel.
Click a table while holding Cmd/Ctrl to insert its name. Click a column to insert table.column.

Autocomplete: table and column suggestions follow the active SQL context. Ctrl/Cmd+Space opens suggestions when the context is valid.

Execution: long queries run in a worker so the interface remains responsive. Cancel stops the worker.

Results: use Shift+click for multi-column sorting, drag column edges to resize, and Ctrl/Cmd+click to freeze columns.", "Use F4 para mostrar ou ocultar este painel.
Clique em uma tabela com Cmd/Ctrl para inserir o nome. Clique em uma coluna para inserir tabela.coluna.

Autocompletar: as sugestões de tabelas e colunas seguem o contexto SQL ativo. Ctrl/Cmd+Espaço abre as sugestões quando o contexto é válido.

Execução: consultas longas rodam em um worker para manter a interface responsiva. Cancelar encerra o worker.

Resultados: use Shift+clique para ordenar por várias colunas, arraste as bordas para redimensionar e use Ctrl/Cmd+clique para congelar colunas.", "Use F4 para mostrar u ocultar este panel.
Haga clic en una tabla con Cmd/Ctrl para insertar su nombre. Haga clic en una columna para insertar tabla.columna.

Autocompletar: las sugerencias de tablas y columnas siguen el contexto SQL activo. Ctrl/Cmd+Espacio abre las sugerencias cuando el contexto es válido.

Ejecución: las consultas largas se ejecutan en un worker para mantener la interfaz receptiva. Cancelar detiene el worker.

Resultados: use Mayús+clic para ordenar por varias columnas, arrastre los bordes para cambiar el tamaño y use Ctrl/Cmd+clic para inmovilizar columnas."] + , ["schema.quickGuideHtml", "Use F4 to show or hide this panel.
Click a table while holding Cmd/Ctrl to insert its name. Click a column to insert table.column.

Autocomplete: table and column suggestions follow the active SQL context. Ctrl/Cmd+Space opens suggestions when the context is valid.

Execution: long queries run in a worker so the interface remains responsive. Cancel stops the worker.

Results: use Shift+click for multi-column sorting, drag column edges to resize, and Ctrl/Cmd+click to freeze columns.

Local backup: settings and saved workspace data stay only in this browser. Periodically export a JSON backup from Settings to avoid loss or continue in another browser. It may contain SQL; review it before sharing.", "Use F4 para mostrar ou ocultar este painel.
Clique em uma tabela com Cmd/Ctrl para inserir o nome. Clique em uma coluna para inserir tabela.coluna.

Autocompletar: as sugestões de tabelas e colunas seguem o contexto SQL ativo. Ctrl/Cmd+Espaço abre as sugestões quando o contexto é válido.

Execução: consultas longas rodam em um worker para manter a interface responsiva. Cancelar encerra o worker.

Resultados: use Shift+clique para ordenar por várias colunas, arraste as bordas para redimensionar e use Ctrl/Cmd+clique para congelar colunas.

Backup local: as configurações e os dados salvos da área de trabalho ficam apenas neste navegador. Exporte periodicamente um backup JSON em Configurações para evitar perdas ou continuar em outro navegador. Ele pode conter SQL; revise-o antes de compartilhar.", "Use F4 para mostrar u ocultar este panel.
Haga clic en una tabla con Cmd/Ctrl para insertar su nombre. Haga clic en una columna para insertar tabla.columna.

Autocompletar: las sugerencias de tablas y columnas siguen el contexto SQL activo. Ctrl/Cmd+Espacio abre las sugerencias cuando el contexto es válido.

Ejecución: las consultas largas se ejecutan en un worker para mantener la interfaz receptiva. Cancelar detiene el worker.

Resultados: use Mayús+clic para ordenar por varias columnas, arrastre los bordes para cambiar el tamaño y use Ctrl/Cmd+clic para inmovilizar columnas.

Copia local: la configuración y los datos guardados del área de trabajo permanecen solo en este navegador. Exporte periódicamente una copia JSON desde Configuración para evitar pérdidas o continuar en otro navegador. Puede contener SQL; revísela antes de compartirla."] , ["sqlMap.description", "Interactive ER diagram with SELECT and JOIN generation from declared foreign keys and session virtual relationships.", "DER interativo com geração de SELECT e JOINs a partir de FKs declaradas e relacionamentos virtuais da sessão.", "Diagrama ER interactivo con generación de SELECT y JOIN a partir de claves foráneas declaradas y relaciones virtuales de la sesión."] , ["sqlMap.selection", "Selection", "Seleção", "Selección"] , ["sqlMap.virtualHint", "Drag one field onto another to create a virtual relationship. Hover over a dotted line to inspect its technical details.", "Arraste um campo sobre outro para criar um relacionamento virtual. Passe o mouse sobre a linha pontilhada para ver os detalhes técnicos.", "Arrastre un campo sobre otro para crear una relación virtual. Pase el puntero sobre una línea de puntos para ver sus detalles técnicos."] @@ -159,7 +159,7 @@ export const I18N_MESSAGE_ROWS = Object.freeze([ , ["shortcuts.quickHistory", "Quick history", "Histórico rápido", "Historial rápido"] , ["help.title", "hSQLite Editor help", "Ajuda do hSQLite Editor", "Ayuda de hSQLite Editor"] , ["help.intro", "A local-first SQLite analysis tool for support, teaching, and technical investigation.", "Uma ferramenta local-first para análise de SQLite em suporte, ensino e investigação técnica.", "Una herramienta local-first para analizar SQLite en soporte, docencia e investigación técnica."] - , ["help.gridHtml", "

1. Getting started

Open a SQLite file, write SQL, and press F5.

2. SQL workspace

Use independent tabs, history, favorites, SQL files, and keyboard shortcuts.

3. Schema

Filter objects by name and type. Select columns to insert qualified identifiers.

4. Results

Sort, paginate, select, and export rows as CSV or JSON.

5. Local data

Session, history, favorites, and preferences stay in this browser.

6. Offline mode

The official standalone HTML works directly from file://.

7. Issues

Use Report a bug to open the project issue tracker.
", "

1. Primeiros passos

Abra um arquivo SQLite, escreva SQL e pressione F5.

2. Área SQL

Use abas independentes, histórico, favoritas, arquivos SQL e atalhos de teclado.

3. Schema

Filtre objetos por nome e tipo. Selecione colunas para inserir identificadores qualificados.

4. Resultados

Ordene, pagine, selecione e exporte registros como CSV ou JSON.

5. Dados locais

Sessão, histórico, favoritas e preferências permanecem neste navegador.

6. Modo offline

O HTML standalone oficial funciona diretamente em file://.

7. Problemas

Use Reportar erro para abrir o rastreador de issues do projeto.
", "

1. Primeros pasos

Abra un archivo SQLite, escriba SQL y pulse F5.

2. Área SQL

Use pestañas independientes, historial, favoritas, archivos SQL y atajos de teclado.

3. Esquema

Filtre objetos por nombre y tipo. Seleccione columnas para insertar identificadores cualificados.

4. Resultados

Ordene, pagine, seleccione y exporte filas como CSV o JSON.

5. Datos locales

La sesión, el historial, las favoritas y las preferencias permanecen en este navegador.

6. Modo sin conexión

El HTML autónomo oficial funciona directamente desde file://.

7. Problemas

Use Informar de un error para abrir el gestor de incidencias del proyecto.
"] + , ["help.gridHtml", "

1. Getting started

Open a SQLite file, write SQL, and press F5.

2. SQL workspace

Use independent tabs, history, favorites, SQL files, and keyboard shortcuts.

3. Schema

Filter objects by name and type. Select columns to insert qualified identifiers.

4. Results

Sort, paginate, select, and export rows as CSV or JSON.

5. Local data and backups

Session data, history, favorites, and preferences are stored only in the current browser profile. Export them periodically from Settings to prevent loss. Import that JSON backup to continue in another browser. Clearing site data, using a private window, or changing browser profiles can make local data unavailable. Backups may contain SQL text, identifiers, history, favorites, and open-tab content; review them before sharing.

6. Offline mode

The official standalone HTML works directly from file://.

7. Issues

Use Report a bug to open the project issue tracker.
", "

1. Primeiros passos

Abra um arquivo SQLite, escreva SQL e pressione F5.

2. Área SQL

Use abas independentes, histórico, favoritas, arquivos SQL e atalhos de teclado.

3. Schema

Filtre objetos por nome e tipo. Selecione colunas para inserir identificadores qualificados.

4. Resultados

Ordene, pagine, selecione e exporte registros como CSV ou JSON.

5. Dados locais e backups

Dados da sessão, histórico, favoritas e preferências ficam armazenados apenas no perfil atual deste navegador. Exporte-os periodicamente em Configurações para evitar perdas. Importe esse backup JSON para continuar em outro navegador. Limpar os dados do site, usar uma janela privativa ou mudar de perfil pode tornar os dados locais indisponíveis. Backups podem conter textos SQL, identificadores, histórico, favoritas e conteúdo das abas abertas; revise-os antes de compartilhar.

6. Modo offline

O HTML standalone oficial funciona diretamente em file://.

7. Problemas

Use Reportar erro para abrir o rastreador de issues do projeto.
", "

1. Primeros pasos

Abra un archivo SQLite, escriba SQL y pulse F5.

2. Área SQL

Use pestañas independientes, historial, favoritas, archivos SQL y atajos de teclado.

3. Esquema

Filtre objetos por nombre y tipo. Seleccione columnas para insertar identificadores cualificados.

4. Resultados

Ordene, pagine, seleccione y exporte filas como CSV o JSON.

5. Datos locales y copias de seguridad

Los datos de la sesión, el historial, las favoritas y las preferencias se almacenan solo en el perfil actual de este navegador. Expórtelos periódicamente desde Configuración para evitar pérdidas. Importe esa copia JSON para continuar en otro navegador. Borrar los datos del sitio, usar una ventana privada o cambiar de perfil puede hacer que los datos locales no estén disponibles. Las copias pueden contener texto SQL, identificadores, historial, favoritas y contenido de las pestañas abiertas; revíselas antes de compartirlas.

6. Modo sin conexión

El HTML autónomo oficial funciona directamente desde file://.

7. Problemas

Use Informar de un error para abrir el gestor de incidencias del proyecto.
"] , ["offline.description", "This artifact is expected to work entirely offline. This screen means the file is incomplete or corrupted.", "Este artefato deve funcionar totalmente offline. Esta tela indica que o arquivo está incompleto ou corrompido.", "Este artefacto debe funcionar totalmente sin conexión. Esta pantalla indica que el archivo está incompleto o dañado."] , ["offline.guideHtml", "Expected: the standalone HTML contains every required resource.

Verify:
  • Build the artifact again with the repository build command.
  • Replace old HTML copies with a fresh artifact from the same release.
  • If the file was modified manually, restore the official generated artifact.
Then open the local file again.", "Esperado: o HTML standalone contém todos os recursos necessários.

Verifique:
  • Gere o artefato novamente com o comando de build do repositório.
  • Substitua cópias antigas do HTML por um artefato novo do mesmo release.
  • Se o arquivo foi modificado manualmente, restaure o artefato oficial gerado.
Depois abra o arquivo local novamente.", "Esperado: el HTML autónomo contiene todos los recursos necesarios.

Compruebe:
  • Genere de nuevo el artefacto con el comando de compilación del repositorio.
  • Sustituya las copias antiguas del HTML por un artefacto nuevo de la misma versión.
  • Si el archivo se modificó manualmente, restaure el artefacto oficial generado.
Después vuelva a abrir el archivo local."] , ["tabs.renameLabel", "Rename tab", "Renomear aba", "Renombrar pestaña"] diff --git a/src/index.template.html b/src/index.template.html index ea1485b..93ef59d 100644 --- a/src/index.template.html +++ b/src/index.template.html @@ -133,6 +133,7 @@

Schema

Arraste a borda direita do cabeçalho para aumentar/diminuir. Duplo clique na borda restaura a largura automática.
Ctrl/Cmd+clique no cabeçalho congela da primeira coluna até a coluna clicada.
Arraste o cabeçalho da coluna para mudar sua posição. +

Backup local: as configurações e os dados salvos da área de trabalho ficam apenas neste navegador. Exporte periodicamente um backup JSON em Configurações para evitar perdas ou continuar em outro navegador. Ele pode conter SQL; revise-o antes de compartilhar.
@@ -725,8 +726,8 @@

4. Resultados

Ordenação multi-coluna, paginação, seleção de linhas, exportação CSV/JSON e cabeçalho sempre visível.
-

5. Persistência

-
Sessão, histórico, favoritos e preferências ficam no navegador (localStorage/IndexedDB).
+

5. Dados locais e backups

+
Dados da sessão, histórico, favoritas e preferências ficam armazenados apenas no perfil atual deste navegador. Exporte-os periodicamente em Configurações para evitar perdas. Importe esse backup JSON para continuar em outro navegador. Limpar os dados do site, usar uma janela privativa ou mudar de perfil pode tornar os dados locais indisponíveis. Backups podem conter textos SQL, identificadores, histórico, favoritas e conteúdo das abas abertas; revise-os antes de compartilhar.

6. Modo offline

From 23e190f85c0ceccbd4036c23f908f9d0d3c7040f Mon Sep 17 00:00:00 2001 From: helbertm Date: Thu, 23 Jul 2026 11:10:17 -0300 Subject: [PATCH 3/4] feat(help): add feature request shortcut --- index.html | 23 +++++++---- .../help-feature-request.test.mjs | 33 ++++++++++++++++ scripts/validate-browser-quality.mjs | 39 +++++++++++++++++++ src/capabilities/03-localization.js | 12 +++--- src/capabilities/03a-localization-messages.js | 2 +- src/index.template.html | 9 +++-- src/styles/00-tokens.css | 6 +-- 7 files changed, 104 insertions(+), 20 deletions(-) create mode 100644 scripts/contract-tests/help-feature-request.test.mjs diff --git a/index.html b/index.html index c2ec5ae..50f1772 100644 --- a/index.html +++ b/index.html @@ -15,7 +15,7 @@ --card-foreground: #e6edf3; --popover: #1a2028; --popover-foreground: #e6edf3; - --primary: #2f81f7; + --primary: #1f6feb; --primary-foreground: #ffffff; --secondary: #242d38; --secondary-foreground: #e6edf3; @@ -63,8 +63,8 @@ --card-foreground: #2a313a; --popover: #eef1f4; --popover-foreground: #2a313a; - --primary: #2f6feb; - --primary-foreground: #f8fbff; + --primary: #0969da; + --primary-foreground: #ffffff; --secondary: #e7ebf0; --secondary-foreground: #2a313a; --muted: #e2e7ed; @@ -5922,11 +5922,14 @@

6. Modo offline

O arquivo oficial deve abrir direto no navegador como um HTML standalone, inclusive em file://.
-

7. Reporte de bug

-
Use o botão Reportar Erros para abrir o GitHub Issues do projeto.
+

7. Problemas e sugestões

+
Use Reportar erro para defeitos reproduzíveis. Sugerir melhoria abre o formulário específico Feature request do GitHub.
- @@ -30135,7 +30138,7 @@

Modo offline indisponível

["shortcuts.quickHistory", "Quick history", "Hist\xF3rico r\xE1pido", "Historial r\xE1pido"], ["help.title", "hSQLite Editor help", "Ajuda do hSQLite Editor", "Ayuda de hSQLite Editor"], ["help.intro", "A local-first SQLite analysis tool for support, teaching, and technical investigation.", "Uma ferramenta local-first para an\xE1lise de SQLite em suporte, ensino e investiga\xE7\xE3o t\xE9cnica.", "Una herramienta local-first para analizar SQLite en soporte, docencia e investigaci\xF3n t\xE9cnica."], - ["help.gridHtml", '

1. Getting started

Open a SQLite file, write SQL, and press F5.

2. SQL workspace

Use independent tabs, history, favorites, SQL files, and keyboard shortcuts.

3. Schema

Filter objects by name and type. Select columns to insert qualified identifiers.

4. Results

Sort, paginate, select, and export rows as CSV or JSON.

5. Local data and backups

Session data, history, favorites, and preferences are stored only in the current browser profile. Export them periodically from Settings to prevent loss. Import that JSON backup to continue in another browser. Clearing site data, using a private window, or changing browser profiles can make local data unavailable. Backups may contain SQL text, identifiers, history, favorites, and open-tab content; review them before sharing.

6. Offline mode

The official standalone HTML works directly from file://.

7. Issues

Use Report a bug to open the project issue tracker.
', '

1. Primeiros passos

Abra um arquivo SQLite, escreva SQL e pressione F5.

2. \xC1rea SQL

Use abas independentes, hist\xF3rico, favoritas, arquivos SQL e atalhos de teclado.

3. Schema

Filtre objetos por nome e tipo. Selecione colunas para inserir identificadores qualificados.

4. Resultados

Ordene, pagine, selecione e exporte registros como CSV ou JSON.

5. Dados locais e backups

Dados da sess\xE3o, hist\xF3rico, favoritas e prefer\xEAncias ficam armazenados apenas no perfil atual deste navegador. Exporte-os periodicamente em Configura\xE7\xF5es para evitar perdas. Importe esse backup JSON para continuar em outro navegador. Limpar os dados do site, usar uma janela privativa ou mudar de perfil pode tornar os dados locais indispon\xEDveis. Backups podem conter textos SQL, identificadores, hist\xF3rico, favoritas e conte\xFAdo das abas abertas; revise-os antes de compartilhar.

6. Modo offline

O HTML standalone oficial funciona diretamente em file://.

7. Problemas

Use Reportar erro para abrir o rastreador de issues do projeto.
', '

1. Primeros pasos

Abra un archivo SQLite, escriba SQL y pulse F5.

2. \xC1rea SQL

Use pesta\xF1as independientes, historial, favoritas, archivos SQL y atajos de teclado.

3. Esquema

Filtre objetos por nombre y tipo. Seleccione columnas para insertar identificadores cualificados.

4. Resultados

Ordene, pagine, seleccione y exporte filas como CSV o JSON.

5. Datos locales y copias de seguridad

Los datos de la sesi\xF3n, el historial, las favoritas y las preferencias se almacenan solo en el perfil actual de este navegador. Exp\xF3rtelos peri\xF3dicamente desde Configuraci\xF3n para evitar p\xE9rdidas. Importe esa copia JSON para continuar en otro navegador. Borrar los datos del sitio, usar una ventana privada o cambiar de perfil puede hacer que los datos locales no est\xE9n disponibles. Las copias pueden contener texto SQL, identificadores, historial, favoritas y contenido de las pesta\xF1as abiertas; rev\xEDselas antes de compartirlas.

6. Modo sin conexi\xF3n

El HTML aut\xF3nomo oficial funciona directamente desde file://.

7. Problemas

Use Informar de un error para abrir el gestor de incidencias del proyecto.
'], + ["help.gridHtml", '

1. Getting started

Open a SQLite file, write SQL, and press F5.

2. SQL workspace

Use independent tabs, history, favorites, SQL files, and keyboard shortcuts.

3. Schema

Filter objects by name and type. Select columns to insert qualified identifiers.

4. Results

Sort, paginate, select, and export rows as CSV or JSON.

5. Local data and backups

Session data, history, favorites, and preferences are stored only in the current browser profile. Export them periodically from Settings to prevent loss. Import that JSON backup to continue in another browser. Clearing site data, using a private window, or changing browser profiles can make local data unavailable. Backups may contain SQL text, identifiers, history, favorites, and open-tab content; review them before sharing.

6. Offline mode

The official standalone HTML works directly from file://.

7. Issues and suggestions

Use Report a bug for reproducible defects. Suggest improvement opens the scoped GitHub Feature request form.
', '

1. Primeiros passos

Abra um arquivo SQLite, escreva SQL e pressione F5.

2. \xC1rea SQL

Use abas independentes, hist\xF3rico, favoritas, arquivos SQL e atalhos de teclado.

3. Schema

Filtre objetos por nome e tipo. Selecione colunas para inserir identificadores qualificados.

4. Resultados

Ordene, pagine, selecione e exporte registros como CSV ou JSON.

5. Dados locais e backups

Dados da sess\xE3o, hist\xF3rico, favoritas e prefer\xEAncias ficam armazenados apenas no perfil atual deste navegador. Exporte-os periodicamente em Configura\xE7\xF5es para evitar perdas. Importe esse backup JSON para continuar em outro navegador. Limpar os dados do site, usar uma janela privativa ou mudar de perfil pode tornar os dados locais indispon\xEDveis. Backups podem conter textos SQL, identificadores, hist\xF3rico, favoritas e conte\xFAdo das abas abertas; revise-os antes de compartilhar.

6. Modo offline

O HTML standalone oficial funciona diretamente em file://.

7. Problemas e sugest\xF5es

Use Reportar erro para defeitos reproduz\xEDveis. Sugerir melhoria abre o formul\xE1rio espec\xEDfico Feature request do GitHub.
', '

1. Primeros pasos

Abra un archivo SQLite, escriba SQL y pulse F5.

2. \xC1rea SQL

Use pesta\xF1as independientes, historial, favoritas, archivos SQL y atajos de teclado.

3. Esquema

Filtre objetos por nombre y tipo. Seleccione columnas para insertar identificadores cualificados.

4. Resultados

Ordene, pagine, seleccione y exporte filas como CSV o JSON.

5. Datos locales y copias de seguridad

Los datos de la sesi\xF3n, el historial, las favoritas y las preferencias se almacenan solo en el perfil actual de este navegador. Exp\xF3rtelos peri\xF3dicamente desde Configuraci\xF3n para evitar p\xE9rdidas. Importe esa copia JSON para continuar en otro navegador. Borrar los datos del sitio, usar una ventana privada o cambiar de perfil puede hacer que los datos locales no est\xE9n disponibles. Las copias pueden contener texto SQL, identificadores, historial, favoritas y contenido de las pesta\xF1as abiertas; rev\xEDselas antes de compartirlas.

6. Modo sin conexi\xF3n

El HTML aut\xF3nomo oficial funciona directamente desde file://.

7. Problemas y sugerencias

Use Informar de un error para defectos reproducibles. Sugerir una mejora abre el formulario espec\xEDfico Feature request de GitHub.
'], ["offline.description", "This artifact is expected to work entirely offline. This screen means the file is incomplete or corrupted.", "Este artefato deve funcionar totalmente offline. Esta tela indica que o arquivo est\xE1 incompleto ou corrompido.", "Este artefacto debe funcionar totalmente sin conexi\xF3n. Esta pantalla indica que el archivo est\xE1 incompleto o da\xF1ado."], ["offline.guideHtml", "Expected: the standalone HTML contains every required resource.

Verify:Then open the local file again.", "Esperado: o HTML standalone cont\xE9m todos os recursos necess\xE1rios.

Verifique:Depois abra o arquivo local novamente.", "Esperado: el HTML aut\xF3nomo contiene todos los recursos necesarios.

Compruebe:Despu\xE9s vuelva a abrir el archivo local."], ["tabs.renameLabel", "Rename tab", "Renomear aba", "Renombrar pesta\xF1a"], @@ -30464,6 +30467,7 @@

Modo offline indisponível

"release.whatsNew": "What's new", "help.label": "Help", "issues.report": "Report a bug", + "issues.suggest": "Suggest improvement", "database.none": "No database loaded", "database.file": "Database file", "database.recent": "Recent", @@ -30471,6 +30475,7 @@

Modo offline indisponível

"session.description": "When enabled, hSQLite Editor stores open tabs, draft queries, history, favorites, and preferences locally in this browser. Use Settings to export or import them on another browser or device.", "theme.toggle": "Switch light or dark theme", "issues.tooltip": "Report a bug in GitHub Issues", + "issues.suggestTooltip": "Suggest improvement: open the GitHub Feature request form in a new tab", "database.actions": "Database file actions", "database.openRecent": "Open recent databases", "database.openFile": "Choose a SQLite file", @@ -30592,6 +30597,7 @@

Modo offline indisponível

"release.whatsNew": "Novidades", "help.label": "Ajuda", "issues.report": "Reportar erro", + "issues.suggest": "Sugerir melhoria", "database.none": "Nenhum banco carregado", "database.file": "Arquivo de dados", "database.recent": "Recentes", @@ -30599,6 +30605,7 @@

Modo offline indisponível

"session.description": "Quando ativado, o hSQLite Editor armazena localmente neste navegador as abas abertas, consultas em edi\xE7\xE3o, hist\xF3rico, favoritas e prefer\xEAncias. Use Configura\xE7\xF5es para exportar ou importar esses dados em outro navegador ou dispositivo.", "theme.toggle": "Alternar tema claro ou escuro", "issues.tooltip": "Reportar um erro no GitHub Issues", + "issues.suggestTooltip": "Sugerir melhoria: abrir o formul\xE1rio Feature request do GitHub em uma nova aba", "database.actions": "A\xE7\xF5es do arquivo de banco de dados", "database.openRecent": "Abrir bancos recentes", "database.openFile": "Escolher um arquivo SQLite", @@ -30720,6 +30727,7 @@

Modo offline indisponível

"release.whatsNew": "Novedades", "help.label": "Ayuda", "issues.report": "Informar de un error", + "issues.suggest": "Sugerir una mejora", "database.none": "No hay ninguna base de datos cargada", "database.file": "Archivo de datos", "database.recent": "Recientes", @@ -30727,6 +30735,7 @@

Modo offline indisponível

"session.description": "Cuando est\xE1 activado, hSQLite Editor guarda localmente en este navegador las pesta\xF1as abiertas, consultas en edici\xF3n, historial, favoritas y preferencias. Usa Configuraci\xF3n para exportar o importar estos datos en otro navegador o dispositivo.", "theme.toggle": "Cambiar entre tema claro y oscuro", "issues.tooltip": "Informar de un error en GitHub Issues", + "issues.suggestTooltip": "Sugerir una mejora: abrir el formulario Feature request de GitHub en una pesta\xF1a nueva", "database.actions": "Acciones del archivo de base de datos", "database.openRecent": "Abrir bases de datos recientes", "database.openFile": "Elegir un archivo SQLite", diff --git a/scripts/contract-tests/help-feature-request.test.mjs b/scripts/contract-tests/help-feature-request.test.mjs new file mode 100644 index 0000000..34b869f --- /dev/null +++ b/scripts/contract-tests/help-feature-request.test.mjs @@ -0,0 +1,33 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; + +const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); +const template = fs.readFileSync(path.join(rootDir, "src/index.template.html"), "utf8"); +const localization = fs.readFileSync(path.join(rootDir, "src/capabilities/03-localization.js"), "utf8"); +const featureForm = fs.readFileSync(path.join(rootDir, ".github/ISSUE_TEMPLATE/feature.yml"), "utf8"); + +test("Help links to the scoped GitHub Feature request form without local data", () => { + const link = template.match(/]+>/)?.[0] || ""; + const href = link.match(/href="([^"]+)"/)?.[1] || ""; + + assert.equal(href, "https://github.com/helbertm/hSQLite-Editor/issues/new?template=feature.yml"); + assert.match(link, /target="_blank"/); + assert.match(link, /rel="noopener noreferrer"/); + assert.match(link, /data-i18n-aria-label="issues\.suggestTooltip"/); + assert.doesNotMatch(href, /(?:body|description|proposal|title)=/i); +}); + +test("Feature request action is localized in every supported locale", () => { + for (const label of ["Suggest improvement", "Sugerir melhoria", "Sugerir una mejora"]) { + assert.match(localization, new RegExp(`"issues\\.suggest": "${label}"`)); + } + assert.equal((localization.match(/"issues\.suggestTooltip":/g) || []).length, 3); +}); + +test("GitHub feature issue form remains available", () => { + assert.match(featureForm, /^name: Feature request$/m); + assert.match(featureForm, /^title: "\[Feature\]: "$/m); +}); diff --git a/scripts/validate-browser-quality.mjs b/scripts/validate-browser-quality.mjs index fac08f6..43868e9 100644 --- a/scripts/validate-browser-quality.mjs +++ b/scripts/validate-browser-quality.mjs @@ -13,6 +13,7 @@ const locales = [ blockedAnnouncement: "Virtual relationships between columns in the same table are not supported.", populationTitle: "Populate table for QA", exportTitle: "Export result", historyTitle: "Query history", favoritesTitle: "Favorite queries", closeTabTitle: "Close tab?", renameTabPrefix: "Rename tab", closeTabPrefix: "Close tab", missingTableCause: "a table or view name was not found", + helpTitle: "hSQLite Editor help", suggestImprovement: "Suggest improvement: open the GitHub Feature request form in a new tab", helpPrefix: "https://learn.microsoft.com/en-us/", csvFilename: "sqlite_result.csv" }, { @@ -24,6 +25,7 @@ const locales = [ blockedAnnouncement: "Relacionamentos virtuais entre colunas da mesma tabela não são suportados.", populationTitle: "Popular tabela para QA", exportTitle: "Exportar resultado", historyTitle: "Histórico de consultas", favoritesTitle: "Consultas favoritas", closeTabTitle: "Fechar aba?", renameTabPrefix: "Renomear aba", closeTabPrefix: "Fechar aba", missingTableCause: "uma tabela ou view não foi encontrada", + helpTitle: "Ajuda do hSQLite Editor", suggestImprovement: "Sugerir melhoria: abrir o formulário Feature request do GitHub em uma nova aba", helpPrefix: "https://learn.microsoft.com/pt-br/", csvFilename: "resultado_sqlite.csv" }, { @@ -35,6 +37,7 @@ const locales = [ blockedAnnouncement: "No se admiten relaciones virtuales entre columnas de la misma tabla.", populationTitle: "Poblar tabla para QA", exportTitle: "Exportar resultado", historyTitle: "Historial de consultas", favoritesTitle: "Consultas favoritas", closeTabTitle: "¿Cerrar pestaña?", renameTabPrefix: "Renombrar pestaña", closeTabPrefix: "Cerrar pestaña", missingTableCause: "no se encontró una tabla o vista", + helpTitle: "Ayuda de hSQLite Editor", suggestImprovement: "Sugerir una mejora: abrir el formulario Feature request de GitHub en una pestaña nueva", helpPrefix: "https://learn.microsoft.com/es-es/", csvFilename: "resultado_sqlite.csv" } ]; @@ -149,6 +152,41 @@ try { `${violation.id}: ${violation.nodes.map(node => node.target.join(" ")).join(" | ")}` )).join(", ")}`); + await page.locator("#helpBtn").click(); + await page.locator("#helpModal").waitFor({ state: "visible" }); + assert(await page.getByRole("dialog", { name: locale.helpTitle }).count() === 1, `${locale.tag}/${viewport.name}: Help dialog name is not localized.`); + const suggestImprovementLink = page.getByRole("link", { name: locale.suggestImprovement }); + assert(await suggestImprovementLink.count() === 1, `${locale.tag}/${viewport.name}: Suggest improvement link is missing or ambiguously named.`); + assert( + await suggestImprovementLink.getAttribute("href") === "https://github.com/helbertm/hSQLite-Editor/issues/new?template=feature.yml", + `${locale.tag}/${viewport.name}: Suggest improvement does not target the scoped Feature request form.` + ); + assert(await suggestImprovementLink.getAttribute("target") === "_blank", `${locale.tag}/${viewport.name}: Suggest improvement does not open in a new tab.`); + assert(await suggestImprovementLink.getAttribute("rel") === "noopener noreferrer", `${locale.tag}/${viewport.name}: Suggest improvement lacks safe new-tab isolation.`); + + const helpAxe = await new AxeBuilder({ page }) + .include("#helpModal") + .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"]) + .analyze(); + assert(helpAxe.violations.length === 0, `${locale.tag}/${viewport.name}: Help violations: ${helpAxe.violations.map(violation => ( + `${violation.id}: ${violation.nodes.map(node => node.target.join(" ")).join(" | ")}` + )).join(", ")}`); + + const helpViewport = page.viewportSize(); + await page.setViewportSize({ width: 320, height: helpViewport.height }); + await page.waitForTimeout(50); + const helpReflow = await page.evaluate(() => ({ + pageOverflow: document.documentElement.scrollWidth > document.documentElement.clientWidth + 1, + modalOverflow: document.querySelector("#helpModal .modal")?.scrollWidth > document.querySelector("#helpModal .modal")?.clientWidth + 1 + })); + assert(!helpReflow.pageOverflow, `${locale.tag}/${viewport.name}: Help causes horizontal page overflow at 320 CSS px.`); + assert(!helpReflow.modalOverflow, `${locale.tag}/${viewport.name}: Help content overflows its modal at 320 CSS px.`); + await page.setViewportSize(helpViewport); + + await page.keyboard.press("Escape"); + await page.locator("#helpModal").waitFor({ state: "hidden" }); + assert(await page.locator("#helpBtn").evaluate(element => element === document.activeElement), `${locale.tag}/${viewport.name}: closing Help did not restore focus.`); + await page.evaluate(async () => { const SqlJs = await initSqlJsIfNeeded(); const database = new SqlJs.Database(); @@ -397,6 +435,7 @@ try { viewport: viewport.name, shellViolations: axe.violations.length, shellSerious: serious.length, + helpViolations: helpAxe.violations.length, tabViolations: tabAxe.violations.length, sqlMapViolations: sqlMapAxe.violations.length, sqlMapSerious: sqlMapSerious.length diff --git a/src/capabilities/03-localization.js b/src/capabilities/03-localization.js index 68d24d8..0432d4c 100644 --- a/src/capabilities/03-localization.js +++ b/src/capabilities/03-localization.js @@ -14,8 +14,8 @@ export const I18N_BASE_MESSAGES = Object.freeze({ "en-US": Object.freeze({ "app.subtitle": "Work and learn with SQLite", "common.open": "Open", "common.save": "Save", "common.close": "Close", "common.cancel": "Cancel", "common.continue": "Continue", "common.copy": "Copy", "common.clear": "Clear", "common.export": "Export", "common.import": "Import", "common.yes": "Yes", "common.no": "No", "common.more": "More", - "language.label": "Language", "language.description": "Choose the interface language.", "session.save": "Save session", "release.whatsNew": "What's new", "help.label": "Help", "issues.report": "Report a bug", "database.none": "No database loaded", "database.file": "Database file", "database.recent": "Recent", "database.new": "New", - "session.description": "When enabled, hSQLite Editor stores open tabs, draft queries, history, favorites, and preferences locally in this browser. Use Settings to export or import them on another browser or device.", "theme.toggle": "Switch light or dark theme", "issues.tooltip": "Report a bug in GitHub Issues", "database.actions": "Database file actions", "database.openRecent": "Open recent databases", "database.openFile": "Choose a SQLite file", "database.newFile": "Create an empty database", "database.saveFile": "Save the current database", + "language.label": "Language", "language.description": "Choose the interface language.", "session.save": "Save session", "release.whatsNew": "What's new", "help.label": "Help", "issues.report": "Report a bug", "issues.suggest": "Suggest improvement", "database.none": "No database loaded", "database.file": "Database file", "database.recent": "Recent", "database.new": "New", + "session.description": "When enabled, hSQLite Editor stores open tabs, draft queries, history, favorites, and preferences locally in this browser. Use Settings to export or import them on another browser or device.", "theme.toggle": "Switch light or dark theme", "issues.tooltip": "Report a bug in GitHub Issues", "issues.suggestTooltip": "Suggest improvement: open the GitHub Feature request form in a new tab", "database.actions": "Database file actions", "database.openRecent": "Open recent databases", "database.openFile": "Choose a SQLite file", "database.newFile": "Create an empty database", "database.saveFile": "Save the current database", "schema.title": "Schema", "schema.other": "Other", "schema.clearFilters": "Clear filters", "schema.empty": "No file loaded.", "schema.quickGuide": "Quick guide", "tabs.new": "New tab", "tabs.label": "SQL statement tabs", "schema.show": "Show Schema", "schema.toggle": "Hide or show Schema", "schema.filter": "Filter schema objects", "schema.filterTypes": "Filter by schema object type", "schema.noObjects": "No objects found.", "editor.title": "SQL statements", "editor.run": "RUN", "editor.openSql": "Open .sql", "editor.saveSql": "Save .sql", "editor.history": "History", "editor.openRun": "Open and run", "editor.closeAll": "Close all tabs", "editor.clearPaste": "Clear and paste", "editor.exportSchema": "Export schema (.sql)", "editor.favorites": "Favorites", "editor.settings": "Settings", "editor.shortcuts": "Shortcuts", "editor.sqlMap": "SQL Map", "editor.drop": "Drop the file here", "editor.dropTypes": ".sql, .txt, or plain-text files", "editor.find": "Find...", "editor.replace": "Replace", "editor.replaceWith": "Replace with...", "editor.replaceAll": "Replace all", @@ -30,8 +30,8 @@ export const I18N_BASE_MESSAGES = Object.freeze({ "pt-BR": Object.freeze({ "app.subtitle": "Para trabalhar e estudar com SQLite", "common.open": "Abrir", "common.save": "Salvar", "common.close": "Fechar", "common.cancel": "Cancelar", "common.continue": "Continuar", "common.copy": "Copiar", "common.clear": "Limpar", "common.export": "Exportar", "common.import": "Importar", "common.yes": "Sim", "common.no": "Não", "common.more": "Mais", - "language.label": "Idioma", "language.description": "Escolha o idioma da interface.", "session.save": "Salvar sessão", "release.whatsNew": "Novidades", "help.label": "Ajuda", "issues.report": "Reportar erro", "database.none": "Nenhum banco carregado", "database.file": "Arquivo de dados", "database.recent": "Recentes", "database.new": "Novo", - "session.description": "Quando ativado, o hSQLite Editor armazena localmente neste navegador as abas abertas, consultas em edição, histórico, favoritas e preferências. Use Configurações para exportar ou importar esses dados em outro navegador ou dispositivo.", "theme.toggle": "Alternar tema claro ou escuro", "issues.tooltip": "Reportar um erro no GitHub Issues", "database.actions": "Ações do arquivo de banco de dados", "database.openRecent": "Abrir bancos recentes", "database.openFile": "Escolher um arquivo SQLite", "database.newFile": "Criar um banco de dados vazio", "database.saveFile": "Salvar o banco de dados atual", + "language.label": "Idioma", "language.description": "Escolha o idioma da interface.", "session.save": "Salvar sessão", "release.whatsNew": "Novidades", "help.label": "Ajuda", "issues.report": "Reportar erro", "issues.suggest": "Sugerir melhoria", "database.none": "Nenhum banco carregado", "database.file": "Arquivo de dados", "database.recent": "Recentes", "database.new": "Novo", + "session.description": "Quando ativado, o hSQLite Editor armazena localmente neste navegador as abas abertas, consultas em edição, histórico, favoritas e preferências. Use Configurações para exportar ou importar esses dados em outro navegador ou dispositivo.", "theme.toggle": "Alternar tema claro ou escuro", "issues.tooltip": "Reportar um erro no GitHub Issues", "issues.suggestTooltip": "Sugerir melhoria: abrir o formulário Feature request do GitHub em uma nova aba", "database.actions": "Ações do arquivo de banco de dados", "database.openRecent": "Abrir bancos recentes", "database.openFile": "Escolher um arquivo SQLite", "database.newFile": "Criar um banco de dados vazio", "database.saveFile": "Salvar o banco de dados atual", "schema.title": "Schema", "schema.other": "Outros", "schema.clearFilters": "Limpar filtros", "schema.empty": "Nenhum arquivo carregado.", "schema.quickGuide": "Guia rápido", "tabs.new": "Nova aba", "tabs.label": "Abas de instruções SQL", "schema.show": "Mostrar Schema", "schema.toggle": "Ocultar ou mostrar Schema", "schema.filter": "Filtrar objetos do schema", "schema.filterTypes": "Filtrar por tipo de objeto do schema", "schema.noObjects": "Nenhum objeto encontrado.", "editor.title": "Instruções SQL", "editor.run": "EXECUTAR", "editor.openSql": "Abrir .sql", "editor.saveSql": "Salvar .sql", "editor.history": "Histórico", "editor.openRun": "Abrir e executar", "editor.closeAll": "Fechar todas as abas", "editor.clearPaste": "Limpar e colar", "editor.exportSchema": "Exportar estrutura (.sql)", "editor.favorites": "Favoritas", "editor.settings": "Configurações", "editor.shortcuts": "Atalhos", "editor.sqlMap": "Mapa SQL", "editor.drop": "Solte o arquivo aqui", "editor.dropTypes": "Arquivos .sql, .txt ou texto puro", "editor.find": "Localizar...", "editor.replace": "Substituir", "editor.replaceWith": "Substituir por...", "editor.replaceAll": "Substituir tudo", @@ -46,8 +46,8 @@ export const I18N_BASE_MESSAGES = Object.freeze({ "es-ES": Object.freeze({ "app.subtitle": "Trabaja y aprende con SQLite", "common.open": "Abrir", "common.save": "Guardar", "common.close": "Cerrar", "common.cancel": "Cancelar", "common.continue": "Continuar", "common.copy": "Copiar", "common.clear": "Limpiar", "common.export": "Exportar", "common.import": "Importar", "common.yes": "Sí", "common.no": "No", "common.more": "Más", - "language.label": "Idioma", "language.description": "Elige el idioma de la interfaz.", "session.save": "Guardar sesión", "release.whatsNew": "Novedades", "help.label": "Ayuda", "issues.report": "Informar de un error", "database.none": "No hay ninguna base de datos cargada", "database.file": "Archivo de datos", "database.recent": "Recientes", "database.new": "Nueva", - "session.description": "Cuando está activado, hSQLite Editor guarda localmente en este navegador las pestañas abiertas, consultas en edición, historial, favoritas y preferencias. Usa Configuración para exportar o importar estos datos en otro navegador o dispositivo.", "theme.toggle": "Cambiar entre tema claro y oscuro", "issues.tooltip": "Informar de un error en GitHub Issues", "database.actions": "Acciones del archivo de base de datos", "database.openRecent": "Abrir bases de datos recientes", "database.openFile": "Elegir un archivo SQLite", "database.newFile": "Crear una base de datos vacía", "database.saveFile": "Guardar la base de datos actual", + "language.label": "Idioma", "language.description": "Elige el idioma de la interfaz.", "session.save": "Guardar sesión", "release.whatsNew": "Novedades", "help.label": "Ayuda", "issues.report": "Informar de un error", "issues.suggest": "Sugerir una mejora", "database.none": "No hay ninguna base de datos cargada", "database.file": "Archivo de datos", "database.recent": "Recientes", "database.new": "Nueva", + "session.description": "Cuando está activado, hSQLite Editor guarda localmente en este navegador las pestañas abiertas, consultas en edición, historial, favoritas y preferencias. Usa Configuración para exportar o importar estos datos en otro navegador o dispositivo.", "theme.toggle": "Cambiar entre tema claro y oscuro", "issues.tooltip": "Informar de un error en GitHub Issues", "issues.suggestTooltip": "Sugerir una mejora: abrir el formulario Feature request de GitHub en una pestaña nueva", "database.actions": "Acciones del archivo de base de datos", "database.openRecent": "Abrir bases de datos recientes", "database.openFile": "Elegir un archivo SQLite", "database.newFile": "Crear una base de datos vacía", "database.saveFile": "Guardar la base de datos actual", "schema.title": "Esquema", "schema.other": "Otros", "schema.clearFilters": "Limpiar filtros", "schema.empty": "No hay ningún archivo cargado.", "schema.quickGuide": "Guía rápida", "tabs.new": "Nueva pestaña", "tabs.label": "Pestañas de sentencias SQL", "schema.show": "Mostrar esquema", "schema.toggle": "Ocultar o mostrar esquema", "schema.filter": "Filtrar objetos del esquema", "schema.filterTypes": "Filtrar por tipo de objeto del esquema", "schema.noObjects": "No se encontraron objetos.", "editor.title": "Sentencias SQL", "editor.run": "EJECUTAR", "editor.openSql": "Abrir .sql", "editor.saveSql": "Guardar .sql", "editor.history": "Historial", "editor.openRun": "Abrir y ejecutar", "editor.closeAll": "Cerrar todas las pestañas", "editor.clearPaste": "Limpiar y pegar", "editor.exportSchema": "Exportar esquema (.sql)", "editor.favorites": "Favoritas", "editor.settings": "Configuración", "editor.shortcuts": "Atajos", "editor.sqlMap": "Mapa SQL", "editor.drop": "Suelta el archivo aquí", "editor.dropTypes": "Archivos .sql, .txt o de texto sin formato", "editor.find": "Buscar...", "editor.replace": "Reemplazar", "editor.replaceWith": "Reemplazar por...", "editor.replaceAll": "Reemplazar todo", diff --git a/src/capabilities/03a-localization-messages.js b/src/capabilities/03a-localization-messages.js index 3df6120..c55f3f5 100644 --- a/src/capabilities/03a-localization-messages.js +++ b/src/capabilities/03a-localization-messages.js @@ -159,7 +159,7 @@ export const I18N_MESSAGE_ROWS = Object.freeze([ , ["shortcuts.quickHistory", "Quick history", "Histórico rápido", "Historial rápido"] , ["help.title", "hSQLite Editor help", "Ajuda do hSQLite Editor", "Ayuda de hSQLite Editor"] , ["help.intro", "A local-first SQLite analysis tool for support, teaching, and technical investigation.", "Uma ferramenta local-first para análise de SQLite em suporte, ensino e investigação técnica.", "Una herramienta local-first para analizar SQLite en soporte, docencia e investigación técnica."] - , ["help.gridHtml", "

1. Getting started

Open a SQLite file, write SQL, and press F5.

2. SQL workspace

Use independent tabs, history, favorites, SQL files, and keyboard shortcuts.

3. Schema

Filter objects by name and type. Select columns to insert qualified identifiers.

4. Results

Sort, paginate, select, and export rows as CSV or JSON.

5. Local data and backups

Session data, history, favorites, and preferences are stored only in the current browser profile. Export them periodically from Settings to prevent loss. Import that JSON backup to continue in another browser. Clearing site data, using a private window, or changing browser profiles can make local data unavailable. Backups may contain SQL text, identifiers, history, favorites, and open-tab content; review them before sharing.

6. Offline mode

The official standalone HTML works directly from file://.

7. Issues

Use Report a bug to open the project issue tracker.
", "

1. Primeiros passos

Abra um arquivo SQLite, escreva SQL e pressione F5.

2. Área SQL

Use abas independentes, histórico, favoritas, arquivos SQL e atalhos de teclado.

3. Schema

Filtre objetos por nome e tipo. Selecione colunas para inserir identificadores qualificados.

4. Resultados

Ordene, pagine, selecione e exporte registros como CSV ou JSON.

5. Dados locais e backups

Dados da sessão, histórico, favoritas e preferências ficam armazenados apenas no perfil atual deste navegador. Exporte-os periodicamente em Configurações para evitar perdas. Importe esse backup JSON para continuar em outro navegador. Limpar os dados do site, usar uma janela privativa ou mudar de perfil pode tornar os dados locais indisponíveis. Backups podem conter textos SQL, identificadores, histórico, favoritas e conteúdo das abas abertas; revise-os antes de compartilhar.

6. Modo offline

O HTML standalone oficial funciona diretamente em file://.

7. Problemas

Use Reportar erro para abrir o rastreador de issues do projeto.
", "

1. Primeros pasos

Abra un archivo SQLite, escriba SQL y pulse F5.

2. Área SQL

Use pestañas independientes, historial, favoritas, archivos SQL y atajos de teclado.

3. Esquema

Filtre objetos por nombre y tipo. Seleccione columnas para insertar identificadores cualificados.

4. Resultados

Ordene, pagine, seleccione y exporte filas como CSV o JSON.

5. Datos locales y copias de seguridad

Los datos de la sesión, el historial, las favoritas y las preferencias se almacenan solo en el perfil actual de este navegador. Expórtelos periódicamente desde Configuración para evitar pérdidas. Importe esa copia JSON para continuar en otro navegador. Borrar los datos del sitio, usar una ventana privada o cambiar de perfil puede hacer que los datos locales no estén disponibles. Las copias pueden contener texto SQL, identificadores, historial, favoritas y contenido de las pestañas abiertas; revíselas antes de compartirlas.

6. Modo sin conexión

El HTML autónomo oficial funciona directamente desde file://.

7. Problemas

Use Informar de un error para abrir el gestor de incidencias del proyecto.
"] + , ["help.gridHtml", "

1. Getting started

Open a SQLite file, write SQL, and press F5.

2. SQL workspace

Use independent tabs, history, favorites, SQL files, and keyboard shortcuts.

3. Schema

Filter objects by name and type. Select columns to insert qualified identifiers.

4. Results

Sort, paginate, select, and export rows as CSV or JSON.

5. Local data and backups

Session data, history, favorites, and preferences are stored only in the current browser profile. Export them periodically from Settings to prevent loss. Import that JSON backup to continue in another browser. Clearing site data, using a private window, or changing browser profiles can make local data unavailable. Backups may contain SQL text, identifiers, history, favorites, and open-tab content; review them before sharing.

6. Offline mode

The official standalone HTML works directly from file://.

7. Issues and suggestions

Use Report a bug for reproducible defects. Suggest improvement opens the scoped GitHub Feature request form.
", "

1. Primeiros passos

Abra um arquivo SQLite, escreva SQL e pressione F5.

2. Área SQL

Use abas independentes, histórico, favoritas, arquivos SQL e atalhos de teclado.

3. Schema

Filtre objetos por nome e tipo. Selecione colunas para inserir identificadores qualificados.

4. Resultados

Ordene, pagine, selecione e exporte registros como CSV ou JSON.

5. Dados locais e backups

Dados da sessão, histórico, favoritas e preferências ficam armazenados apenas no perfil atual deste navegador. Exporte-os periodicamente em Configurações para evitar perdas. Importe esse backup JSON para continuar em outro navegador. Limpar os dados do site, usar uma janela privativa ou mudar de perfil pode tornar os dados locais indisponíveis. Backups podem conter textos SQL, identificadores, histórico, favoritas e conteúdo das abas abertas; revise-os antes de compartilhar.

6. Modo offline

O HTML standalone oficial funciona diretamente em file://.

7. Problemas e sugestões

Use Reportar erro para defeitos reproduzíveis. Sugerir melhoria abre o formulário específico Feature request do GitHub.
", "

1. Primeros pasos

Abra un archivo SQLite, escriba SQL y pulse F5.

2. Área SQL

Use pestañas independientes, historial, favoritas, archivos SQL y atajos de teclado.

3. Esquema

Filtre objetos por nombre y tipo. Seleccione columnas para insertar identificadores cualificados.

4. Resultados

Ordene, pagine, seleccione y exporte filas como CSV o JSON.

5. Datos locales y copias de seguridad

Los datos de la sesión, el historial, las favoritas y las preferencias se almacenan solo en el perfil actual de este navegador. Expórtelos periódicamente desde Configuración para evitar pérdidas. Importe esa copia JSON para continuar en otro navegador. Borrar los datos del sitio, usar una ventana privada o cambiar de perfil puede hacer que los datos locales no estén disponibles. Las copias pueden contener texto SQL, identificadores, historial, favoritas y contenido de las pestañas abiertas; revíselas antes de compartirlas.

6. Modo sin conexión

El HTML autónomo oficial funciona directamente desde file://.

7. Problemas y sugerencias

Use Informar de un error para defectos reproducibles. Sugerir una mejora abre el formulario específico Feature request de GitHub.
"] , ["offline.description", "This artifact is expected to work entirely offline. This screen means the file is incomplete or corrupted.", "Este artefato deve funcionar totalmente offline. Esta tela indica que o arquivo está incompleto ou corrompido.", "Este artefacto debe funcionar totalmente sin conexión. Esta pantalla indica que el archivo está incompleto o dañado."] , ["offline.guideHtml", "Expected: the standalone HTML contains every required resource.

Verify:
  • Build the artifact again with the repository build command.
  • Replace old HTML copies with a fresh artifact from the same release.
  • If the file was modified manually, restore the official generated artifact.
Then open the local file again.", "Esperado: o HTML standalone contém todos os recursos necessários.

Verifique:
  • Gere o artefato novamente com o comando de build do repositório.
  • Substitua cópias antigas do HTML por um artefato novo do mesmo release.
  • Se o arquivo foi modificado manualmente, restaure o artefato oficial gerado.
Depois abra o arquivo local novamente.", "Esperado: el HTML autónomo contiene todos los recursos necesarios.

Compruebe:
  • Genere de nuevo el artefacto con el comando de compilación del repositorio.
  • Sustituya las copias antiguas del HTML por un artefacto nuevo de la misma versión.
  • Si el archivo se modificó manualmente, restaure el artefacto oficial generado.
Después vuelva a abrir el archivo local."] , ["tabs.renameLabel", "Rename tab", "Renomear aba", "Renombrar pestaña"] diff --git a/src/index.template.html b/src/index.template.html index 93ef59d..575dd3b 100644 --- a/src/index.template.html +++ b/src/index.template.html @@ -734,11 +734,14 @@

6. Modo offline

O arquivo oficial deve abrir direto no navegador como um HTML standalone, inclusive em file://.
-

7. Reporte de bug

-
Use o botão Reportar Erros para abrir o GitHub Issues do projeto.
+

7. Problemas e sugestões

+
Use Reportar erro para defeitos reproduzíveis. Sugerir melhoria abre o formulário específico Feature request do GitHub.
-
diff --git a/src/styles/00-tokens.css b/src/styles/00-tokens.css index ef2e5da..84a7c00 100644 --- a/src/styles/00-tokens.css +++ b/src/styles/00-tokens.css @@ -6,7 +6,7 @@ --card-foreground: #e6edf3; --popover: #1a2028; --popover-foreground: #e6edf3; - --primary: #2f81f7; + --primary: #1f6feb; --primary-foreground: #ffffff; --secondary: #242d38; --secondary-foreground: #e6edf3; @@ -54,8 +54,8 @@ html[data-theme="light"] { --card-foreground: #2a313a; --popover: #eef1f4; --popover-foreground: #2a313a; - --primary: #2f6feb; - --primary-foreground: #f8fbff; + --primary: #0969da; + --primary-foreground: #ffffff; --secondary: #e7ebf0; --secondary-foreground: #2a313a; --muted: #e2e7ed; From 2d7aaa941989128cbd15627fe1e598b1494a9ba8 Mon Sep 17 00:00:00 2001 From: helbertm Date: Thu, 23 Jul 2026 12:51:23 -0300 Subject: [PATCH 4/4] fix(release): support external attestation bundles --- package-lock.json | 8 ++ package.json | 1 + sbom.spdx.json | 24 ++++ .../contract-tests/github-controls.test.mjs | 110 ++++++++++++++++++ scripts/validate-github-controls.mjs | 105 ++++++++++++++++- 5 files changed, 247 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 015ade1..8b37456 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@playwright/test": "1.61.1", "acorn": "8.17.0", "esbuild": "0.28.1", + "snappyjs": "0.7.0", "sql.js": "1.14.1" }, "engines": { @@ -728,6 +729,13 @@ "node": ">=18" } }, + "node_modules/snappyjs": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/snappyjs/-/snappyjs-0.7.0.tgz", + "integrity": "sha512-u5iEEXkMe2EInQio6Wv9LWHOQYRDbD2O9hzS27GpT/lwfIQhTCnHCTqedqHIHe9ZcvQo+9au6vngQayipz1NYw==", + "dev": true, + "license": "MIT" + }, "node_modules/sql.js": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.14.1.tgz", diff --git a/package.json b/package.json index 07c2536..edfd13c 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "@playwright/test": "1.61.1", "acorn": "8.17.0", "esbuild": "0.28.1", + "snappyjs": "0.7.0", "sql.js": "1.14.1" } } diff --git a/sbom.spdx.json b/sbom.spdx.json index 9a642a5..40294cb 100644 --- a/sbom.spdx.json +++ b/sbom.spdx.json @@ -877,6 +877,25 @@ } ] }, + { + "name": "snappyjs", + "SPDXID": "SPDXRef-Package-snappyjs-0.7.0", + "versionInfo": "0.7.0", + "downloadLocation": "https://registry.npmjs.org/snappyjs/-/snappyjs-0.7.0.tgz", + "filesAnalyzed": false, + "licenseConcluded": "MIT", + "licenseDeclared": "MIT", + "copyrightText": "NOASSERTION", + "primaryPackagePurpose": "LIBRARY", + "comment": "Development or release dependency from package-lock.json.", + "externalRefs": [ + { + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:npm/snappyjs@0.7.0" + } + ] + }, { "name": "sql.js", "SPDXID": "SPDXRef-Package-sql.js-1.14.1", @@ -1231,6 +1250,11 @@ "relationshipType": "DEV_DEPENDENCY_OF", "relatedSpdxElement": "SPDXRef-Package-hSQLite-Editor" }, + { + "spdxElementId": "SPDXRef-Package-snappyjs-0.7.0", + "relationshipType": "DEV_DEPENDENCY_OF", + "relatedSpdxElement": "SPDXRef-Package-hSQLite-Editor" + }, { "spdxElementId": "SPDXRef-Package-hSQLite-Editor", "relationshipType": "DEPENDS_ON", diff --git a/scripts/contract-tests/github-controls.test.mjs b/scripts/contract-tests/github-controls.test.mjs index 226bec1..faddf5f 100644 --- a/scripts/contract-tests/github-controls.test.mjs +++ b/scripts/contract-tests/github-controls.test.mjs @@ -4,11 +4,15 @@ import { spawnSync } from "node:child_process"; import test from "node:test"; import { fileURLToPath } from "node:url"; import { + decodeAttestationBundle, evaluateGithubControls, + hydrateAttestationResponse, + isTrustedAttestationBundleUrl, renderPolicyTemplate, selectGithubToken, VERDICTS } from "../validate-github-controls.mjs"; +import SnappyJS from "snappyjs"; const policy = { repository: "helbertm/hSQLite-Editor", @@ -251,6 +255,112 @@ test("prefers GH_TOKEN without exposing or rewriting credential values", () => { assert.equal(selectGithubToken({}), ""); }); +test("accepts only credential-free HTTPS attestation bundle URLs", () => { + assert.equal( + isTrustedAttestationBundleUrl("https://example.blob.core.windows.net/attestations/bundle.json.sn?sig=redacted"), + true + ); + for (const unsafeUrl of [ + "http://example.com/bundle.json", + "https://user:secret@example.com/bundle.json", + "file:///tmp/bundle.json", + "not-a-url" + ]) { + assert.equal(isTrustedAttestationBundleUrl(unsafeUrl), false); + } +}); + +test("decodes bounded Snappy attestation bundles from the current GitHub API", () => { + const bundle = attestation("https://slsa.dev/provenance/v1").bundle; + const compressed = SnappyJS.compress(Buffer.from(JSON.stringify(bundle))); + const decoded = decodeAttestationBundle(compressed, "application/x-snappy"); + assert.deepEqual(decoded, bundle); + assert.throws( + () => decodeAttestationBundle(Buffer.alloc((1024 * 1024) + 1), "application/x-snappy"), + /size is outside the accepted range/ + ); +}); + +test("hydrates bundle-url-only attestations without changing policy evaluation", async () => { + const responses = passingResponses(); + const bundleUrl = "https://example.blob.core.windows.net/attestations/bundle.json.sn?sig=redacted"; + const provenance = attestation("https://slsa.dev/provenance/v1"); + const hydrated = await hydrateAttestationResponse( + { + status: 200, + body: { attestations: [{ repository_id: 42, bundle_url: bundleUrl }] } + }, + async url => { + assert.equal(url, bundleUrl); + return { status: 200, body: provenance.bundle }; + } + ); + responses.attestations.provenance = hydrated; + const result = evaluateGithubControls({ + policy, + version, + responses, + manualConfirmations: { pagesAdminBypassDisabled: true } + }); + assert.equal(result.exitCode, 0); +}); + +test("treats unavailable bundle URLs as transport failures", async () => { + const response = await hydrateAttestationResponse( + { + status: 200, + body: { + attestations: [{ + repository_id: 42, + bundle_url: "https://example.blob.core.windows.net/attestations/missing.json.sn" + }] + } + }, + async () => ({ status: 503, body: null }) + ); + assert.equal(response.transportError, true); + const responses = passingResponses(); + responses.attestations.provenance = response; + const result = evaluateGithubControls({ + policy, + version, + responses, + manualConfirmations: { pagesAdminBypassDisabled: true } + }); + assert.equal(result.exitCode, 3); +}); + +test("keeps predicate and digest checks strict after bundle URL hydration", async () => { + for (const bundle of [ + attestation("https://spdx.dev/Document/v2.3").bundle, + attestation("https://slsa.dev/provenance/v1", `sha256:${"d".repeat(64)}`).bundle + ]) { + const responses = passingResponses(); + responses.attestations.provenance = await hydrateAttestationResponse( + { + status: 200, + body: { + attestations: [{ + repository_id: 42, + bundle_url: "https://example.blob.core.windows.net/attestations/invalid.json.sn" + }] + } + }, + async () => ({ status: 200, body: bundle }) + ); + const result = evaluateGithubControls({ + policy, + version, + responses, + manualConfirmations: { pagesAdminBypassDisabled: true } + }); + assert.equal(result.exitCode, 1); + assert.ok(result.findings.some(finding => + finding.control === "provenance attestation" && finding.verdict === VERDICTS.FAIL + )); + } +}); + test("release policy templates reject unsafe version input", () => { assert.equal(renderPolicyTemplate("hSQLite-Editor-v{version}.html", version), "hSQLite-Editor-v0.3.143.html"); for (const unsafeVersion of ["0.3.143/../../x", "0.3.143%2f..", "0.3.143