oficina / slides Public

Take the sign-in door's copy from estilo

The door's words were Slides' own catalog keys, and the submit read
"Email me a link", which Paul ruled out on 2026-10-01: it reads "Sign
in". estilo feat/door-copy now owns the whole copy set (chrome.English),
and an app override no longer compiles, so every app says the same
thing.

Pin estilo fbd14e7. The form drops Heading/AddressLabel/Help/Submit; the
sent state is SignInNotice.Sent(again), keeping the return_to retry
link; the signed-out page (standalone and connect's "signedout" state
alike) is SignInNotice.SignedOut("/signin"). Tab titles come from
.Page(), since the layout appends " · Slides". The sign-in email's
subject is chrome.EmailSubject("Slides"), "Sign in to Slides", instead
of rastrillo's "Your sign-in link".

Deleted the catalog keys the door now owns (slides.signin.title,
address.label, help, submit, sent.title, sent, again; slides.signedout.*;
slides.home.signedout_*, unused since renderHome draws the door's page).

Must not merge before estilo feat/door-copy lands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 2b54a2feebfd0396db304a0c0e3a4694e2732fa0 parent c722306
6 files changed, +77 −69
  • go.mod +1 −1
  • go.sum +2 −14
  • internal/slides/auth.go +14 −30
  • internal/slides/home_test.go +6 −1
  • internal/slides/locales/en.toml +6 −23
  • internal/slidestest/signin_door_test.go +48 −0

CI passed — run details

CI log
=== gate ===
=== rust: fmt
=== rust: clippy
   Compiling proc-macro2 v1.0.107
   Compiling quote v1.0.47
   Compiling unicode-ident v1.0.24
   Compiling serde_core v1.0.229
   Compiling version_check v0.9.5
   Compiling zerocopy v0.8.56
    Checking cfg-if v1.0.4
   Compiling serde v1.0.229
    Checking once_cell v1.21.4
    Checking smallvec v1.15.2
   Compiling libc v0.2.189
   Compiling rustversion v1.0.23
   Compiling zmij v1.0.23
   Compiling autocfg v1.5.1
   Compiling parking_lot_core v0.9.12
   Compiling crossbeam-utils v0.8.22
    Checking pin-project-lite v0.2.17
    Checking parking v2.2.1
    Checking scopeguard v1.2.0
   Compiling libm v0.2.16
   Compiling thiserror v2.0.20
    Checking event-listener v5.4.2
   Compiling serde_json v1.0.151
   Compiling paste v1.0.15
    Checking lock_api v0.4.14
    Checking memchr v2.8.3
   Compiling ahash v0.8.12
    Checking foldhash v0.2.0
    Checking event-listener-strategy v0.5.4
    Checking itoa v1.0.18
   Compiling num-traits v0.2.19
    Checking smallstr v0.3.1
    Checking async-lock v3.4.2
    Checking fastrand v2.5.0 (/home/paulca/.local/state/amadan/runner/repos/oficina__slides/wt/chore__door-copy/core/vendor/fastrand-2.5.0)
    Checking downcast-rs v1.2.1
    Checking equivalent v1.0.2
    Checking indexmap-nostd v0.4.0
    Checking bitflags v2.13.1
    Checking spin v0.9.9
    Checking semver v1.0.28
    Checking arrayvec v0.7.8
    Checking multi-stash v0.2.0
    Checking wasmparser-nostd v0.100.2
    Checking arc-swap v1.9.2
   Compiling syn v3.0.4
   Compiling syn v2.0.119
    Checking wasmi_core v0.32.3
    Checking hashbrown v0.17.1
   Compiling num-derive v0.4.2
    Checking indexmap v2.14.1
   Compiling serde_derive v1.0.229
   Compiling thiserror-impl v2.0.20
   Compiling async-trait v0.1.92
    Checking hashbrown v0.14.5
    Checking dashmap v6.2.1
    Checking string-interner v0.17.0
    Checking yrs v0.23.5
    Checking wasmparser v0.258.0
    Checking wasmi_collections v0.32.3
    Checking wasmi v0.32.3
    Checking slides-core v0.1.0 (/home/paulca/.local/state/amadan/runner/repos/oficina__slides/wt/chore__door-copy/core)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.57s
=== wasm: the artifact the host embeds
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s
   Compiling proc-macro2 v1.0.107
   Compiling unicode-ident v1.0.24
   Compiling quote v1.0.47
   Compiling serde_core v1.0.229
   Compiling cfg-if v1.0.4
   Compiling pin-project-lite v0.2.17
   Compiling rustversion v1.0.23
   Compiling smallvec v1.15.2
   Compiling zmij v1.0.23
   Compiling parking_lot_core v0.9.12
   Compiling getrandom v0.4.3
   Compiling crossbeam-utils v0.8.22
   Compiling scopeguard v1.2.0
   Compiling thiserror v2.0.20
   Compiling serde_json v1.0.151
   Compiling serde v1.0.229
   Compiling lock_api v0.4.14
   Compiling event-listener v5.4.2
   Compiling hashbrown v0.14.5
   Compiling memchr v2.8.3
   Compiling once_cell v1.21.4
   Compiling itoa v1.0.18
   Compiling smallstr v0.3.1
   Compiling event-listener-strategy v0.5.4
   Compiling fastrand v2.5.0 (/home/paulca/.local/state/amadan/runner/repos/oficina__slides/wt/chore__door-copy/core/vendor/fastrand-2.5.0)
   Compiling async-lock v3.4.2
   Compiling arc-swap v1.9.2
   Compiling dashmap v6.2.1
   Compiling syn v3.0.4
   Compiling serde_derive v1.0.229
   Compiling thiserror-impl v2.0.20
   Compiling async-trait v0.1.92
   Compiling yrs v0.23.5
   Compiling slides-core v0.1.0 (/home/paulca/.local/state/amadan/runner/repos/oficina__slides/wt/chore__door-copy/core)
    Finished `release` profile [optimized] target(s) in 6.51s
core/target/wasm32-unknown-unknown/release/slides_core.wasm (1228229 bytes)
=== the embedded wasm must match a fresh build
=== rust: test
   Compiling cfg-if v1.0.4
   Compiling once_cell v1.21.4
   Compiling smallvec v1.15.2
   Compiling pin-project-lite v0.2.17
   Compiling parking v2.2.1
   Compiling serde_core v1.0.229
   Compiling zerocopy v0.8.56
   Compiling libc v0.2.189
   Compiling scopeguard v1.2.0
   Compiling zmij v1.0.23
   Compiling crossbeam-utils v0.8.22
   Compiling itoa v1.0.18
   Compiling foldhash v0.2.0
   Compiling memchr v2.8.3
   Compiling thiserror v2.0.20
   Compiling arc-swap v1.9.2
   Compiling libm v0.2.16
   Compiling lock_api v0.4.14
   Compiling event-listener v5.4.2
   Compiling num-traits v0.2.19
   Compiling smallstr v0.3.1
   Compiling downcast-rs v1.2.1
   Compiling fastrand v2.5.0 (/home/paulca/.local/state/amadan/runner/repos/oficina__slides/wt/chore__door-copy/core/vendor/fastrand-2.5.0)
   Compiling indexmap-nostd v0.4.0
   Compiling equivalent v1.0.2
   Compiling semver v1.0.28
   Compiling arrayvec v0.7.8
   Compiling multi-stash v0.2.0
   Compiling bitflags v2.13.1
   Compiling event-listener-strategy v0.5.4
   Compiling spin v0.9.9
   Compiling async-lock v3.4.2
   Compiling wasmparser-nostd v0.100.2
   Compiling parking_lot_core v0.9.12
   Compiling wasmi_core v0.32.3
   Compiling serde v1.0.229
   Compiling hashbrown v0.17.1
   Compiling serde_json v1.0.151
   Compiling indexmap v2.14.1
   Compiling wasmparser v0.258.0
   Compiling ahash v0.8.12
   Compiling hashbrown v0.14.5
   Compiling string-interner v0.17.0
   Compiling dashmap v6.2.1
   Compiling wasmi_collections v0.32.3
   Compiling yrs v0.23.5
   Compiling wasmi v0.32.3
   Compiling slides-core v0.1.0 (/home/paulca/.local/state/amadan/runner/repos/oficina__slides/wt/chore__door-copy/core)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 7.76s
     Running unittests src/lib.rs (core/target/debug/deps/slides_core-929bfceb2edd4a94)

running 30 tests
test layout::tests::aside_region_rect ... ok
test check::tests::the_empty_document_passes ... ok
test layout::tests::default_region_by_kind ... ok
test layout::tests::heading_region_rect ... ok
test layout::tests::body_region_rect_narrows_for_an_aside ... ok
test layout::tests::media_region_rect ... ok
test layout::tests::to_canvas_narrows_body_when_an_aside_is_present ... ok
test check::tests::what_the_ops_write_passes ... ok
test markdown::tests::marks_nest_and_links_carry_marks ... ok
test markdown::tests::paragraphs_split_on_blank_lines_and_list_markers ... ok
test markdown::tests::empty_input_is_no_paragraphs_and_lines_keep_blanks ... ok
test layout::tests::to_flow_classifies_by_position_and_kind ... ok
test check::tests::y_prosemirror_shapes_pass ... ok
test markdown::tests::inline_marks ... ok
test layout::tests::to_canvas_splits_a_shared_region_equally_and_sets_z_by_index ... ok
test layout::tests::to_flow_is_a_no_op_when_already_in_order ... ok
test schema::tests::an_empty_map_reads_as_a_set_mark ... ok
test check::tests::bare_text_at_fragment_level_is_reported ... ok
test schema::tests::an_href_map_without_a_string_url_reads_as_no_link ... ok
test check::tests::a_non_paragraph_child_is_reported ... ok
test check::tests::an_element_inside_a_paragraph_is_reported ... ok
test schema::tests::href_is_written_as_a_one_key_map_and_reads_back ... ok
test check::tests::an_unknown_formatting_attribute_is_reported ... ok
test layout::tests::to_flow_orders_y_then_x ... ok
test schema::tests::href_written_as_a_plain_string_still_reads_back ... ok
test view::tests::malformed_box_is_null_and_phone_keeps_only_typed_fields ... ok
test view::tests::assets_are_typed_or_skipped ... ok
test view::tests::malformed_fonts_are_null ... ok
test view::tests::malformed_background_is_the_default ... ok
test layout::tests::to_flow_keeps_a_concurrent_text_edit ... ok

test result: ok. 30 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/no_host_imports.rs (core/target/debug/deps/no_host_imports-184c0d8bf00e14f9)

running 1 test
test no_host_imports ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/ops.rs (core/target/debug/deps/ops-256addce27f366d1)

running 12 tests
test add_slide_then_view_lists_it ... ok
test ops_are_atomic ... ok
test add_block_with_markdown_produces_runs ... ok
test layout_switch_assigns_boxes ... ok
test remove_block_drops_it_from_columns_children ... ok
test duplicate_slide_remaps_columns_children ... ok
test columns_children_must_name_blocks_on_the_slide ... ok
test columns_children_have_one_parent_and_no_cycles ... ok
test update_roundtrip ... ok
test move_slide_keeps_a_concurrent_text_edit ... ok
test move_block_keeps_a_concurrent_text_edit ... ok
test remaining_ops_roundtrip ... ok

test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

   Doc-tests slides_core

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

=== rust: fmt (pdf)
=== rust: clippy (pdf)
   Compiling proc-macro2 v1.0.107
   Compiling quote v1.0.47
   Compiling unicode-ident v1.0.24
   Compiling autocfg v1.5.1
    Checking arrayvec v0.7.8
   Compiling libm v0.2.16
    Checking log v0.4.34
   Compiling serde_core v1.0.229
    Checking simd-adler32 v0.3.10
    Checking adler2 v2.0.1
    Checking smallvec v1.16.0
    Checking bitflags v2.13.1
   Compiling crc32fast v1.5.1
    Checking cfg-if v1.0.4
   Compiling version_check v0.9.5
    Checking foldhash v0.2.0
   Compiling libc v0.2.189
    Checking equivalent v1.0.2
    Checking arrayref v0.3.9
    Checking memchr v2.8.3
    Checking unicode-script v0.5.8
    Checking siphasher v1.0.3
    Checking miniz_oxide v0.9.1
    Checking fdeflate v0.3.7
    Checking miniz_oxide v0.8.9
    Checking polycool v0.4.0
    Checking unicode-ccc v0.4.0
    Checking unicode-bidi-mirroring v0.4.0
    Checking roxmltree v0.20.0
   Compiling slotmap v1.1.1
    Checking unicode-properties v0.1.4
    Checking tinyvec_macros v0.1.1
    Checking kurbo v0.13.1
    Checking tinyvec v1.13.2
    Checking weezl v0.1.12
    Checking zune-core v0.5.3
    Checking color_quant v1.1.0
    Checking quick-error v2.0.1
   Compiling num-traits v0.2.19
    Checking base64 v0.22.1
    Checking byteorder-lite v0.1.0
    Checking imagesize v0.14.0
    Checking flate2 v1.1.10
    Checking zune-jpeg v0.5.15
    Checking fontconfig-parser v0.5.8
    Checking roxmltree v0.21.1
    Checking gif v0.14.2
    Checking simplecss v0.2.2
    Checking unicode-bidi v0.3.18
    Checking image-webp v0.2.4
    Checking pico-args v0.5.0
    Checking png v0.18.1
    Checking rustc-hash v2.1.3
    Checking ryu v1.0.23
    Checking stable_deref_trait v1.2.1
    Checking itoa v1.0.18
    Checking data-url v0.3.2
    Checking xmlwriter v0.1.0
    Checking unicode-vo v0.1.0
   Compiling serde v1.0.229
    Checking bumpalo v3.20.3
    Checking xmp-writer v0.3.3
    Checking pdf-writer v0.15.0
    Checking semver v1.0.28
   Compiling syn v3.0.5
   Compiling syn v2.0.119
    Checking svgtypes v0.16.1
    Checking core_maths v0.1.1
    Checking ttf-parser v0.25.1
    Checking float-cmp v0.9.0
    Checking strict-num v0.1.1
    Checking memmap2 v0.9.11
    Checking hashbrown v0.17.1
    Checking indexmap v2.14.2
   Compiling bytemuck_derive v1.12.0
    Checking fontdb v0.23.0
   Compiling synstructure v0.13.2
    Checking wasmparser v0.258.0
   Compiling zerofrom-derive v0.1.7
   Compiling yoke-derive v0.8.2
    Checking bytemuck v1.25.2
    Checking font-types v0.11.3
    Checking tiny-skia-path v0.12.0
    Checking rustybuzz v0.20.1
    Checking rgb v0.8.53
    Checking zerofrom v0.1.8
    Checking yoke v0.8.3
    Checking read-fonts v0.39.2
    Checking tiny-skia v0.12.0
    Checking usvg v0.47.0
    Checking resvg v0.47.0
    Checking write-fonts v0.48.1
    Checking skrifa v0.42.1
    Checking subsetter v0.2.6
    Checking krilla v0.8.2
    Checking krilla-svg v0.8.1
    Checking slides-pdf v0.1.0 (/home/paulca/.local/state/amadan/runner/repos/oficina__slides/wt/chore__door-copy/core/pdf)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 8.12s
=== wasm: the pdf artifact the host embeds
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s
   Compiling proc-macro2 v1.0.107
   Compiling quote v1.0.47
   Compiling unicode-ident v1.0.24
   Compiling autocfg v1.5.1
   Compiling simd-adler32 v0.3.10
   Compiling arrayvec v0.7.8
   Compiling libm v0.2.16
   Compiling log v0.4.34
   Compiling crc32fast v1.5.1
   Compiling smallvec v1.16.0
   Compiling adler2 v2.0.1
   Compiling cfg-if v1.0.4
   Compiling bitflags v2.13.1
   Compiling version_check v0.9.5
   Compiling arrayref v0.3.9
   Compiling memchr v2.8.3
   Compiling tinyvec_macros v0.1.1
   Compiling fdeflate v0.3.7
   Compiling miniz_oxide v0.9.1
   Compiling miniz_oxide v0.8.9
   Compiling hashbrown v0.17.1
   Compiling unicode-ccc v0.4.0
   Compiling polycool v0.4.0
   Compiling unicode-properties v0.1.4
   Compiling unicode-bidi-mirroring v0.4.0
   Compiling siphasher v1.0.3
   Compiling unicode-script v0.5.8
   Compiling num-traits v0.2.19
   Compiling equivalent v1.0.2
   Compiling kurbo v0.13.1
   Compiling tinyvec v1.13.2
   Compiling slotmap v1.1.1
   Compiling zune-core v0.5.3
   Compiling memmap2 v0.9.11
   Compiling weezl v0.1.12
   Compiling flate2 v1.1.10
   Compiling quick-error v2.0.1
   Compiling indexmap v2.14.2
   Compiling color_quant v1.1.0
   Compiling imagesize v0.14.0
   Compiling byteorder-lite v0.1.0
   Compiling base64 v0.22.1
   Compiling png v0.18.1
   Compiling image-webp v0.2.4
   Compiling gif v0.14.2
   Compiling syn v3.0.5
   Compiling syn v2.0.119
   Compiling zune-jpeg v0.5.15
   Compiling roxmltree v0.21.1
   Compiling core_maths v0.1.1
   Compiling simplecss v0.2.2
   Compiling ttf-parser v0.25.1
   Compiling float-cmp v0.9.0
   Compiling svgtypes v0.16.1
   Compiling strict-num v0.1.1
   Compiling unicode-bidi v0.3.18
   Compiling xmlwriter v0.1.0
   Compiling rustc-hash v2.1.3
   Compiling data-url v0.3.2
   Compiling ryu v1.0.23
   Compiling stable_deref_trait v1.2.1
   Compiling unicode-vo v0.1.0
   Compiling itoa v1.0.18
   Compiling pico-args v0.5.0
   Compiling pdf-writer v0.15.0
   Compiling xmp-writer v0.3.3
   Compiling bumpalo v3.20.3
   Compiling bytemuck_derive v1.12.0
   Compiling synstructure v0.13.2
   Compiling fontdb v0.23.0
   Compiling zerofrom-derive v0.1.7
   Compiling yoke-derive v0.8.2
   Compiling bytemuck v1.25.2
   Compiling font-types v0.11.3
   Compiling tiny-skia-path v0.12.0
   Compiling rustybuzz v0.20.1
   Compiling zerofrom v0.1.8
   Compiling rgb v0.8.53
   Compiling yoke v0.8.3
   Compiling read-fonts v0.39.2
   Compiling tiny-skia v0.12.0
   Compiling usvg v0.47.0
   Compiling resvg v0.47.0
   Compiling write-fonts v0.48.1
   Compiling skrifa v0.42.1
   Compiling subsetter v0.2.6
   Compiling krilla v0.8.2
   Compiling krilla-svg v0.8.1
   Compiling slides-pdf v0.1.0 (/home/paulca/.local/state/amadan/runner/repos/oficina__slides/wt/chore__door-copy/core/pdf)
    Finished `release` profile [optimized] target(s) in 14.62s
core/pdf/target/wasm32-unknown-unknown/release/slides_pdf.wasm (5301002 bytes)
=== the embedded pdf wasm must match a fresh build
=== rust: test (pdf)
   Compiling simd-adler32 v0.3.10
   Compiling log v0.4.34
   Compiling arrayvec v0.7.8
   Compiling smallvec v1.16.0
   Compiling adler2 v2.0.1
   Compiling bitflags v2.13.1
   Compiling cfg-if v1.0.4
   Compiling foldhash v0.2.0
   Compiling serde_core v1.0.229
   Compiling libm v0.2.16
   Compiling arrayref v0.3.9
   Compiling memchr v2.8.3
   Compiling equivalent v1.0.2
   Compiling num-traits v0.2.19
   Compiling bytemuck v1.25.2
   Compiling libc v0.2.189
   Compiling crc32fast v1.5.1
   Compiling unicode-ccc v0.4.0
   Compiling unicode-script v0.5.8
   Compiling miniz_oxide v0.9.1
   Compiling miniz_oxide v0.8.9
   Compiling polycool v0.4.0
   Compiling fdeflate v0.3.7
   Compiling unicode-bidi-mirroring v0.4.0
   Compiling siphasher v1.0.3
   Compiling tinyvec_macros v0.1.1
   Compiling unicode-properties v0.1.4
   Compiling font-types v0.11.3
   Compiling roxmltree v0.20.0
   Compiling tinyvec v1.13.2
   Compiling slotmap v1.1.1
   Compiling kurbo v0.13.1
   Compiling byteorder-lite v0.1.0
   Compiling color_quant v1.1.0
   Compiling flate2 v1.1.10
   Compiling read-fonts v0.39.2
   Compiling weezl v0.1.12
   Compiling base64 v0.22.1
   Compiling quick-error v2.0.1
   Compiling imagesize v0.14.0
   Compiling zune-core v0.5.3
   Compiling image-webp v0.2.4
   Compiling float-cmp v0.9.0
   Compiling fontconfig-parser v0.5.8
   Compiling gif v0.14.2
   Compiling zune-jpeg v0.5.15
   Compiling strict-num v0.1.1
   Compiling roxmltree v0.21.1
   Compiling core_maths v0.1.1
   Compiling tiny-skia-path v0.12.0
   Compiling ttf-parser v0.25.1
   Compiling memmap2 v0.9.11
   Compiling png v0.18.1
   Compiling zerofrom v0.1.8
   Compiling simplecss v0.2.2
   Compiling unicode-vo v0.1.0
   Compiling rustc-hash v2.1.3
   Compiling svgtypes v0.16.1
   Compiling unicode-bidi v0.3.18
   Compiling data-url v0.3.2
   Compiling pico-args v0.5.0
   Compiling xmlwriter v0.1.0
   Compiling itoa v1.0.18
   Compiling stable_deref_trait v1.2.1
   Compiling ryu v1.0.23
   Compiling rgb v0.8.53
   Compiling yoke v0.8.3
   Compiling pdf-writer v0.15.0
   Compiling bumpalo v3.20.3
   Compiling xmp-writer v0.3.3
   Compiling semver v1.0.28
   Compiling tiny-skia v0.12.0
   Compiling hashbrown v0.17.1
   Compiling serde v1.0.229
   Compiling indexmap v2.14.2
   Compiling rustybuzz v0.20.1
   Compiling fontdb v0.23.0
   Compiling wasmparser v0.258.0
   Compiling usvg v0.47.0
   Compiling resvg v0.47.0
   Compiling write-fonts v0.48.1
   Compiling skrifa v0.42.1
   Compiling subsetter v0.2.6
   Compiling krilla v0.8.2
   Compiling krilla-svg v0.8.1
   Compiling slides-pdf v0.1.0 (/home/paulca/.local/state/amadan/runner/repos/oficina__slides/wt/chore__door-copy/core/pdf)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 10.94s
     Running unittests src/lib.rs (core/pdf/target/debug/deps/slides_pdf-891fafaa44f9fd64)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests/no_host_imports.rs (core/pdf/target/debug/deps/no_host_imports-17ccbb1d62120093)

running 1 test
test no_host_imports ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

     Running tests/render.rs (core/pdf/target/debug/deps/render-2e1d7e8b19d78595)

running 6 tests
test a_null_zero_length_pair_is_no_frames_not_a_dereference ... ok
test text_with_no_fonts_at_all_still_renders_the_shapes ... ok
test raster_image_is_embedded ... ok
test abi_frames_round_trip ... ok
test renders_text_as_embedded_glyphs ... ok
test two_svgs_make_two_pages ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

   Doc-tests slides_pdf

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

=== fixtures: node dependencies
v24.19.0
11.17.0
=== fixtures: npm ci (needs the registry — see .amadan/README.md)

added 19 packages, and audited 20 packages in 501ms

5 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
npm warn allow-scripts 1 package has install scripts not yet covered by allowScripts:
npm warn allow-scripts   esbuild@0.28.2 (postinstall: node install.js)
npm warn allow-scripts
npm warn allow-scripts Run `npm approve-scripts --allow-scripts-pending` to review, or `npm approve-scripts <pkg>` to allow.
=== fixtures: the JS side's own tests
✔ one finished upload is register_asset plus the block that uses it, in one batch (10.124473ms)
✔ a record missing its numbers still makes an op the core accepts (0.933034ms)
✔ unusedAssets is what no slide refers to, and remove_asset drops one (2.356448ms)
✔ the Unused images list reconciles by id and names each Remove button (0.949625ms)
✔ an upload refusal is said in words the person can act on (0.219437ms)
✔ clampBox keeps the box inside the frame and no smaller than the minimum (2.219885ms)
✔ fixBox writes four decimals (0.350481ms)
✔ guides are the thirds plus every other block edge (0.301099ms)
✔ snapMove pulls the nearest edge onto a guide within one per cent, and leaves a far one (0.524945ms)
✔ snapResize moves only the edges the handle drags (0.266886ms)
✔ moveBox and resizeBox by handle (0.303904ms)
✔ nudge: arrows move by half a per cent, Shift by two, Alt resizes (0.283547ms)
✔ rectBox reads a block rectangle against its frame (0.31184ms)
✔ typing into a block is a text change on its slide only (8.931656ms)
✔ a block's props changing is structure on its slide (1.428153ms)
✔ adding, moving or removing a block is structure on its slide (1.987594ms)
✔ the notes are text on their slide (1.036947ms)
✔ a slide added, removed or moved is an order change (1.569526ms)
✔ one transaction touching several things reports each once (0.875637ms)
✔ what the ops write passes, and so does an empty document (10.187059ms)
✔ the shapes y-prosemirror and older documents write pass (1.140269ms)
✔ an unknown formatting attribute is reported with the core's message (1.287202ms)
✔ an element inside a paragraph, a non-paragraph child and bare text are reported (1.983175ms)
✔ the unknownmark poison shape is what the check refuses (1.194129ms)
✔ y-prosemirror 1.3.7 deletes the words under an unknown mark; sanitising first keeps them (4.012857ms)
✔ the vocabulary is the five marks and nothing else (0.167631ms)
✔ a reload reuses the id this tab stored for the deck (1.243ms)
✔ two tabs, and two decks in one tab, get different ids (1.442591ms)
✔ a stored value that is not an id, or storage that throws, gives a fresh id (0.359668ms)
✔ a duplicated tab gives up the copied id; the original keeps it (5.882751ms)
✔ a reload finds no rival and keeps its id (2.639064ms)
✔ presence switches id: beats carry the new one and the old one is somebody else (1.98038ms)
✔ an empty body is no frames (1.708935ms)
✔ frames come back in order with their exact bytes (0.388973ms)
✔ a length prefix is big-endian (0.195954ms)
✔ a truncated frame throws rather than returning a short update (1.739472ms)
✔ deck controls are set_meta (13.362919ms)
✔ slide controls are set_slide, and background keeps its colour (2.837933ms)
✔ move up and down are move_slide with the right after (1.673059ms)
✔ block props: value kinds, region, a shape label (1.701081ms)
✔ box fields are set_block on the desktop and set_phone in the phone view (3.975327ms)
✔ move block and the layer buttons (1.73295ms)
✔ add block: region after the selection on flow, a default box on canvas, a heading level that fits (6.895082ms)
✔ a block is named by its type and its first words (1.132915ms)
✔ blockChange: a control element to its op (1.463881ms)
✔ the Blocks list reconciles by id and keeps its buttons (0.913868ms)
✔ open resolves to the button that closed it and the trimmed field (1.947379ms)
✔ a second open while the dialog is up joins the first (0.345672ms)
✔ a missing dialog mounts as nothing (0.133268ms)
✔ a tab advances its own cursor only when its POST is the very next row (2.332674ms)
✔ the cursor never moves backwards when a response is overtaken by this tab's own write (0.657411ms)
✔ a later response still carries the cursor forward (0.313914ms)
✔ a burst of notifications collapses into one fetch at a time (0.278948ms)
✔ an event naming a seq the tab already has fetches nothing (0.207586ms)
✔ a stopped subscriber drops a response that arrives late (0.301981ms)
✔ stop closes the stream, and start opens exactly one (0.216422ms)
✔ a hidden tab closes its stream on a shortened deadline, and activity reopens it (0.970303ms)
✔ switching away and back inside the grace never drops the stream, so nothing has to be caught up (0.278358ms)
✔ a visible tab that is only being READ closes when idle and comes back on any activity (0.346985ms)
✔ activity while streaming defers the idle close rather than reopening anything (0.200763ms)
✔ a stream failure is never reported as a save failure, and heals on activity (0.266535ms)
✔ an empty response advances the cursor without applying anything (0.216442ms)
✔ a failed fetch leaves the cursor alone so the next notification retries it (1.387278ms)
✔ a decode failure retries once through the raw log, then gives up (0.261225ms)
✔ updatesURL spells the query string exactly, in particular raw as the literal raw=1 (0.164626ms)
✔ empty_doc_views_as_defaults (3.299281ms)
✔ set_meta (2.521385ms)
✔ slides: add, move, remove, set_slide, set_notes (6.879913ms)
✔ duplicate_slide (4.801602ms)
✔ blocks: add, move, remove, set_block (4.592103ms)
✔ set_phone (1.457729ms)
✔ table cells follow rows and cols (2.017629ms)
✔ canvas blocks: box defaults from the region, z from the index (1.526206ms)
✔ assets: register and remove (1.581669ms)
✔ markdown: every mark, code blocks are plain lines (6.826635ms)
✔ batch_is_atomic (2.149514ms)
✔ a batch applies in one transaction with the given origin (0.514876ms)
✔ columns_children_validated (1.215208ms)
✔ layout_switch_round_trip (1.131742ms)
✔ view_matches_snapshot_fixtures (12.534611ms)
✔ columns children have one parent and no cycles (codex 7) (1.189941ms)
✔ canvas-to-flow keeps a concurrent text edit (codex 4) (2.679128ms)
✔ move_block keeps a concurrent text edit, and later inserts land where after says (codex 4) (3.315771ms)
✔ move_slide keeps a concurrent text edit, and later inserts land where after says (codex 4 at slide level) (2.65876ms)
✔ format: {name} and {n} interpolate; an unknown key is left as text (0.648304ms)
✔ a beat on connect, then one every twenty seconds (2.493032ms)
✔ a move posts once, 200ms after the last of a burst, and re-arms the beat (1.505246ms)
✔ a hidden tab stops beating; coming back posts at once (0.330755ms)
✔ applySet leaves this tab out and answers what changed (0.572704ms)
✔ a set with rubbish in it is read for what it does carry (0.262087ms)
✔ initials, and a client id per tab (3.91261ms)
✔ a resync that fails is retried with backoff until it succeeds, without ever running twice at once (1.984247ms)
✔ the browser's online event retries immediately, not just on the next backoff tick (0.272035ms)
✔ reportDivergence before any failure runs the first attempt without a caller ever calling online (3.303838ms)
✔ a permanent 4xx drops its batch instead of retrying it, and does not poison the next edit (83.059635ms)
✔ a network failure or a 5xx is retried, not dropped (60.365347ms)
✔ a 500 is retried the same way a network failure is, never dropped (60.398048ms)
✔ the core-written structure reads as ProseMirror marks (4.949176ms)
✔ a y-prosemirror round trip preserves bold and the link (1.965552ms)
✔ y-prosemirror writes href as the one-key map and marks as {} (0.692958ms)
✔ a plain-string href, written before the map shape, still reads (0.470344ms)
✔ textFragment writes the same shape as writeText (0.442031ms)
✔ safeHref is the server allowlist, and a disallowed href never draws an anchor (0.330875ms)
✔ parseHash splits the slide and the block, and survives a stray % (2.175762ms)
✔ destroy takes the listeners off, and a second mount leaves one of each (0.738302ms)
✔ formatHash is the inverse (1.392427ms)
✔ textBlocks finds the text-bearing blocks and skips the rest (1.688407ms)
✔ mountTarget is the element whose children are the paragraphs (0.199431ms)
✔ paragraphTag keeps the renderer's own markup per type (0.121115ms)
✔ blockFragment finds a block's text by slide and block id (5.307903ms)
✔ notesFragment finds the slide's notes (0.724546ms)
✔ blockIDs and slideIDs are the orders on the stage and the strip (0.72134ms)
✔ cellFragments are a table's cells, row-major (1.003685ms)
✔ a load that fails leaves the stage on the slide it was showing and says so (1.076491ms)
✔ slideAfter is the slide to show once one is gone (0.157803ms)
✔ the queue words map to savestatus states, failed staying failed (0.917956ms)
✔ a dropped batch outranks the queue, for the rest of the session (0.187469ms)
✔ the editor status: the save line and the load error are two lines (0.287595ms)
✔ mountSaveStatus loads estilo's module and mounts it on the line (0.919038ms)
✔ a save-status module that will not load says so and refuses to mount (0.464723ms)
✔ dropTarget: before puts it ahead of the item, after behind it, null means first (1.333819ms)
✔ a drop that leaves the order as it is is no move (0.180035ms)
✔ dropOp is the move_slide (0.715479ms)
✔ labelFor is the slide's first heading, else the numbered fallback (3.950451ms)
✔ flush re-points every dirty thumbnail and relabels it (1.321265ms)
✔ destroy takes the item-menu listener off; a second mount leaves one (0.228855ms)
✔ plan says what to add, drop and where each item goes (0.596378ms)
✔ sync moves only the items out of place and keeps focus (0.924678ms)
✔ desktop view: box and size are the block; hidden and order are always the phone map (1.406334ms)
✔ phone view: everything goes to set_phone, carrying the rest of the map (0.359207ms)
✔ the ops it makes are ones the document takes (8.017698ms)
✔ phoneMap reads only the four keys, and only well-typed (0.178461ms)
✔ the stored view is read defensively (0.444987ms)
✔ linkAt reads the link under the cursor and nothing elsewhere (1.886906ms)
✔ setLink marks the selection (1.774187ms)
✔ setLink on a collapsed cursor inside a link replaces that link (0.489039ms)
✔ setLink with nothing selected and no link does nothing (0.175937ms)
✔ removeLink clears the link under the cursor (0.323812ms)
✔ the keymap binds every documented shortcut (0.185715ms)
✔ Mod-b toggles bold through the keymap (0.471125ms)
✔ setLink refuses an address outside the allowlist and writes nothing (0.417155ms)
✔ the state machine runs choose → sign → put → register → op (2.520013ms)
✔ a file the deck will not take never reaches the network (0.561563ms)
✔ alt text is required before the bytes move, unless the image is decorative (0.267717ms)
✔ a step that fails stops the ones after it and reports its own code (0.479501ms)
✔ a failure that is not ours still becomes an UploadError (0.265664ms)
✔ a second pick while one is in flight is refused, not raced (0.413809ms)
✔ an unreadable size is zero rather than a failure (3.444399ms)
✔ a per-call applyOp wins, so an upload lands on the block it was chosen for (0.234105ms)
✔ checkFile takes exactly the four types (0.358747ms)
✔ sha256Hex is the hex digest of the bytes (8.148721ms)
✔ refusal reads this app's JSON error shape, and survives one that is not (0.322809ms)
✔ malformed fonts are null (2.809891ms)
✔ a malformed background is the theme default (1.417394ms)
✔ a malformed box is null (0.622837ms)
✔ the phone layer keeps only its typed fields, order as a whole number (1.463719ms)
✔ assets are typed or skipped (0.35004ms)
✔ every poison update is deterministic bytes that apply to any deck (6.379414ms)
ℹ tests 158
ℹ suites 0
ℹ pass 158
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 279.459041
=== the vectors must match a fresh generation
canvas: 22 ops, 3715 bytes
columns: 23 ops, 3134 bytes
every-block: 36 ops, 3180 bytes
table: 14 ops, 1509 bytes
title-only: 6 ops, 1136 bytes
=== the editor bundle must match a fresh build
wrote internal/slides/static/editor.0463180baf5aab9f.js (376416 bytes)
=== go: vet, gofmt, test, migration check
go vet ./...
go: downloading amadan.net/oficina/estilo.git v0.0.0-20261001075355-fbd14e759027
go test ./...
?   	amadan.net/oficina/slides/cmd/slides	[no test files]
ok  	amadan.net/oficina/slides/internal/slides	11.224s
ok  	amadan.net/oficina/slides/internal/slides/core	2.494s
ok  	amadan.net/oficina/slides/internal/slides/deck	0.007s
ok  	amadan.net/oficina/slides/internal/slides/fonts	0.013s
?   	amadan.net/oficina/slides/internal/slides/icons	[no test files]
ok  	amadan.net/oficina/slides/internal/slides/layout	0.017s
ok  	amadan.net/oficina/slides/internal/slides/live	0.007s
ok  	amadan.net/oficina/slides/internal/slides/pdfcore	3.737s
ok  	amadan.net/oficina/slides/internal/slides/present	0.006s
ok  	amadan.net/oficina/slides/internal/slides/render	0.016s
ok  	amadan.net/oficina/slides/internal/slides/svg	0.013s
ok  	amadan.net/oficina/slides/internal/slidestest	36.488s
?   	amadan.net/oficina/slides/scripts/writeestilo	[no test files]
go run amadan.net/rastrillo/rastrillo/cmd/rastrillo migration check
rastrillo migration check: models and migrations agree
python3 scripts/credits.py --check
credits: checked
gate: ok
diff --git a/go.mod b/go.mod
index 031a7ea..359cb0c 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.25.0
toolchain go1.26.5
require (
- amadan.net/oficina/estilo.git v0.0.0-20260929185827-41201439fd22
+ amadan.net/oficina/estilo.git v0.0.0-20261001075355-fbd14e759027
amadan.net/oficina/home/connect v0.0.0-20260923190447-01166fcfc0a9
amadan.net/oficina/home/handoff v0.0.0-20260908102137-481d2d57f489
amadan.net/rastrillo/rastrillo v0.26.1-0.20260908075707-29e4491631bd
diff --git a/go.sum b/go.sum
index 17bb1ce..5ef1c95 100644
--- a/go.sum
+++ b/go.sum
@@ -1,17 +1,5 @@
-amadan.net/oficina/estilo.git v0.0.0-20260924071126-894afdcb71a9 h1:vHOkWeJ7mRwLjPbMihf0f7aqz4CLSe0/NseQsHcJUYQ=
-amadan.net/oficina/estilo.git v0.0.0-20260924071126-894afdcb71a9/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw=
-amadan.net/oficina/estilo.git v0.0.0-20260929070530-5b2ce835e773 h1:9DIAhG3TaGG+LE2+8LvU73ZEhfK+oNn2y2p4kFjAAi0=
-amadan.net/oficina/estilo.git v0.0.0-20260929070530-5b2ce835e773/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw=
-amadan.net/oficina/estilo.git v0.0.0-20260929095046-d0664ee49914 h1:V0AyxXYCxqGZClOLqvppw+cCTXsn3b9NmRQ8CV5ZlXY=
-amadan.net/oficina/estilo.git v0.0.0-20260929095046-d0664ee49914/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw=
-amadan.net/oficina/estilo.git v0.0.0-20260929095838-0994675f496e h1:LTSmIWUS8RjPb6m94Ynf7moyFYeBXr08rdOr3nmNqvg=
-amadan.net/oficina/estilo.git v0.0.0-20260929095838-0994675f496e/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw=
-amadan.net/oficina/estilo.git v0.0.0-20260929165053-846b39772f3c h1:YYocIz3r3b4oIA0K/58JkSsmyTOCzMT/2GaW/MFbbPo=
-amadan.net/oficina/estilo.git v0.0.0-20260929165053-846b39772f3c/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw=
-amadan.net/oficina/estilo.git v0.0.0-20260929183653-cc2c1e56fb4b h1:UOxYBKsOSRHcHHYNKt0i6cB1Z4yT2soywOTWURYvBWo=
-amadan.net/oficina/estilo.git v0.0.0-20260929183653-cc2c1e56fb4b/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw=
-amadan.net/oficina/estilo.git v0.0.0-20260929185827-41201439fd22 h1:RR9qP85FU1cZdn788v1zSLgZbKMGBmSk6Q7Rezj5JYI=
-amadan.net/oficina/estilo.git v0.0.0-20260929185827-41201439fd22/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw=
+amadan.net/oficina/estilo.git v0.0.0-20261001075355-fbd14e759027 h1:+NFDWW5ROkLaFIsT9flDuAjNeQJJxj0Safk32UcRyn0=
+amadan.net/oficina/estilo.git v0.0.0-20261001075355-fbd14e759027/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw=
amadan.net/oficina/home/connect v0.0.0-20260923190447-01166fcfc0a9 h1:K/PaOVdQqRMwifPD6ipiwLeGR5nbFSxm9FxjrDJlZcg=
amadan.net/oficina/home/connect v0.0.0-20260923190447-01166fcfc0a9/go.mod h1:YroavXe43Wietec+po8mtSk4a9Bir2h31Q3iAf4MWxI=
amadan.net/oficina/home/handoff v0.0.0-20260908102137-481d2d57f489 h1:RPKtyry/71i06o8L+gjAaUyexbmG56kO1YPs601URAM=
diff --git a/internal/slides/auth.go b/internal/slides/auth.go
index 94a1cd5..884fea1 100644
--- a/internal/slides/auth.go
+++ b/internal/slides/auth.go
@@ -278,6 +278,7 @@ func newAuthPlugin(cfg Config, writer *sql.DB, list allowlist) (*rauth.Auth, err
Origin: cfg.Origin,
InstanceKey: cfg.InstanceKey,
Mailer: cfg.Mailer,
+ Subject: chrome.EmailSubject("Slides"),
Authorize: list.allows,
SigninPath: signinPath,
Logger: cfg.Logger,
@@ -608,30 +609,20 @@ func (a *app) signin(w http.ResponseWriter, r *http.Request) {
if remembered, ok := a.rememberedReturn(r); ok {
again += "?return_to=" + url.QueryEscape(remembered)
}
+ sent := chrome.SignInNotice{Icon: brand.Path("slides"), App: "Slides", Sky: sky.For(r.Host)}.Sent(again)
render(w, "signin", signinView{
- shell: a.publicShell(r, frame{parts: []string{rastrillo.T(r, "slides.signin.sent.title")}, signinPage: true}),
- Sent: &chrome.SignInNotice{
- Icon: brand.Path("slides"),
- App: "Slides",
- Sky: sky.For(r.Host),
- Heading: rastrillo.T(r, "slides.signin.sent.title"),
- Body: rastrillo.T(r, "slides.signin.sent"),
- Link: &chrome.Link{Href: again, Label: rastrillo.T(r, "slides.signin.again")},
- },
+ shell: a.publicShell(r, frame{parts: []string{sent.Page()}, signinPage: true}),
+ Sent: &sent,
})
return
}
form := chrome.SignIn{
- Icon: brand.Path("slides"),
- App: "Slides",
- Sky: sky.For(r.Host),
- Heading: rastrillo.T(r, "slides.signin.title"),
- Action: signinPath,
- Hidden: []chrome.Hidden{{Name: "force", Value: "1"}},
- AddressLabel: rastrillo.T(r, "slides.signin.address.label"),
- Help: rastrillo.T(r, "slides.signin.help"),
- Submit: rastrillo.T(r, "slides.signin.submit"),
- OpenSource: &chrome.Link{Href: "/help/open-source", Label: rastrillo.T(r, "slides.credits.title")},
+ Icon: brand.Path("slides"),
+ App: "Slides",
+ Sky: sky.For(r.Host),
+ Action: signinPath,
+ Hidden: []chrome.Hidden{{Name: "force", Value: "1"}},
+ OpenSource: &chrome.Link{Href: "/help/open-source", Label: rastrillo.T(r, "slides.credits.title")},
}
if hasReturn {
form.Return = ret
@@ -655,7 +646,7 @@ func (a *app) signin(w http.ResponseWriter, r *http.Request) {
form.Error = rastrillo.T(r, "slides.signin.error.unknown")
}
render(w, "signin", signinView{
- shell: a.publicShell(r, frame{parts: []string{form.Heading}, signinPage: true}),
+ shell: a.publicShell(r, frame{parts: []string{form.Page()}, signinPage: true}),
SignIn: form,
})
}
@@ -871,18 +862,11 @@ type signedOutView struct {
// renderSignedOut is where Sign out lands, standalone and with Home
// alike: "You're signed out", and a primary Sign in.
func (a *app) renderSignedOut(w http.ResponseWriter, r *http.Request, status int) {
- title := rastrillo.T(r, "slides.signedout.title")
+ notice := chrome.SignInNotice{Icon: brand.Path("slides"), App: "Slides", Sky: sky.For(r.Host)}.SignedOut(signinPath)
var buf strings.Builder
err := pages["signed_out"].ExecuteTemplate(&buf, "layout", signedOutView{
- shell: a.publicShell(r, frame{parts: []string{title}, signinPage: true}),
- Notice: chrome.SignInNotice{
- Icon: brand.Path("slides"),
- App: "Slides",
- Sky: sky.For(r.Host),
- Heading: rastrillo.T(r, "slides.signedout.heading"),
- Body: rastrillo.T(r, "slides.signedout.body"),
- Link: &chrome.Link{Href: signinPath, Label: rastrillo.T(r, "slides.chrome.signin")},
- },
+ shell: a.publicShell(r, frame{parts: []string{notice.Page()}, signinPage: true}),
+ Notice: notice,
})
if err != nil {
a.logger.Error("slides: render signed-out page", "err", err)
diff --git a/internal/slides/home_test.go b/internal/slides/home_test.go
index 79b323d..b089805 100644
--- a/internal/slides/home_test.go
+++ b/internal/slides/home_test.go
@@ -201,6 +201,11 @@ func TestHomeFrontDoorLocalRecoveryAndCallbackGuard(t *testing.T) {
if tc.path == "/signin/local" && !strings.Contains(w.Body.String(), "estilo-signin-form") {
t.Error("local form missing")
}
+ // connect's "signedout" state draws the door's signed-out page,
+ // never connect's own "Sign in with Home".
+ if tc.path == "/signed-out" && (!strings.Contains(w.Body.String(), "You’re signed out") || strings.Contains(w.Body.String(), "with Home")) {
+ t.Errorf("Home-mode signed-out page is not the door's:\n%s", w.Body.String())
+ }
}
r := httptest.NewRequest("GET", homeTestOrigin+"/signin/local", nil)
c := homeSession(t, a, "home-person", connect.HomeMethod)
@@ -258,7 +263,7 @@ func TestHomeDifferentAddressStaysInLocalRecovery(t *testing.T) {
if sentPage.Code != http.StatusOK {
t.Fatalf("sent page: %d", sentPage.Code)
}
- m := regexp.MustCompile(`href="([^"]+)">slides\.signin\.again`).FindStringSubmatch(sentPage.Body.String())
+ m := regexp.MustCompile(`href="([^"]+)">Use a different address`).FindStringSubmatch(sentPage.Body.String())
if m == nil {
t.Fatal("sent page has no Use a different address link")
}
diff --git a/internal/slides/locales/en.toml b/internal/slides/locales/en.toml
index da32d6e..334e5f6 100644
--- a/internal/slides/locales/en.toml
+++ b/internal/slides/locales/en.toml
@@ -7,38 +7,24 @@
# Adding a language means adding a file beside this one with the same
# keys; nothing else in the app has to change.
-# slides.signin.* — GET /signin (auth.go's signin handler), estilo's
-# sign-in pages with the suite's copy deck (shared chrome spec §9.2). The
-# page renders the form and, on the way back from rastrillo/auth,
-# whichever outcome it was redirected here with (?sent=1, or
-# ?err=rate|address|expired|keymail|removed|1).
+# slides.signin.* — GET /signin (auth.go's signin handler): the errors
+# estilo's sign-in door shows, on the way back from rastrillo/auth, for
+# whichever outcome it was redirected here with
+# (?err=rate|address|expired|keymail|removed|1). The door's own words
+# (form, check-your-email, signed out) are estilo's chrome.English, not
+# keys here, so no app can drift from the others (shared chrome spec §9.2).
#
# error.unknown covers both the keymail outcome and a bare failure:
# rastrillo/auth's Verify deliberately answers the same way for an
# unknown, a used and an expired token, so there is no finer
# distinction for this page to draw — and every outcome on this screen
# means one operational thing, "ask for a new link".
-#
-# sent says "that address", not the address: gateBegin answers every
-# POST with the same bare ?sent=1, so the page has no address to echo.
-slides.signin.title = "Sign in"
-slides.signin.address.label = "Email address"
-slides.signin.help = "We’ll email you a link that signs you in. It works once and expires in 15 minutes."
-slides.signin.submit = "Email me a link"
-slides.signin.sent.title = "Check your email"
-slides.signin.sent = "If that address can sign in here, we’ve sent it a link. It works once and expires in 15 minutes."
-slides.signin.again = "Use a different address"
slides.signin.error.rate = "Too many attempts. Wait a few minutes, then try again."
slides.signin.error.address = "That doesn’t look like an email address."
slides.signin.error.expired = "That link has expired or was already used. Enter your email to get a new one."
slides.signin.error.unknown = "We couldn’t sign you in. Enter your email to get a new link."
slides.signin.error.removed = "Your access has been removed, so you’ve been signed out. Ask whoever runs this team if that’s wrong."
-# slides.signedout.* — where Sign out lands, standalone and with Home.
-slides.signedout.title = "Signed out"
-slides.signedout.heading = "You’re signed out"
-slides.signedout.body = "You’ve signed out of Slides on this device."
-
# slides.chrome.* — estilo's shared bar and account menu (chrome.go).
slides.chrome.skip = "Skip to content"
slides.chrome.menu = "Menu"
@@ -604,9 +590,6 @@ slides.export.error.store = "The PDF was made but couldn't be stored. Try again.
# slides.home.* — connect's account-connection pages. The hub is never
# named: it is "your Oficina account", and the verb is Connect (Paul's
# answer 3 to the shared chrome spec's §13).
-slides.home.signedout_title = "Signed out"
-slides.home.signedout_body = "You’ve signed out of Slides on this device."
-slides.home.signedout_action = "Sign in"
slides.home.start_title = "Connect your Oficina account"
slides.home.start_body = "Use your Oficina account to sign in to this Slides account. Your decks and sharing settings stay as they are."
slides.home.start_action = "Continue"
diff --git a/internal/slidestest/signin_door_test.go b/internal/slidestest/signin_door_test.go
index 8557cf0..52e88ce 100644
--- a/internal/slidestest/signin_door_test.go
+++ b/internal/slidestest/signin_door_test.go
@@ -35,3 +35,51 @@ func TestSignInPageIsTheDoor(t *testing.T) {
t.Error("sign-in page does not link sky.css")
}
}
+
+// TestDoorCopyIsEstilos pins the door's words, which are estilo's, not
+// this app's: the submit reads "Sign in" and never "Email me a link"
+// (Paul, 2026-10-01), the hub is never named, and the three tab titles
+// are "<Page> · Slides".
+func TestDoorCopyIsEstilos(t *testing.T) {
+ h := newApp(t)
+ title := regexp.MustCompile(`<title>([^<]*)</title>`)
+ for _, tc := range []struct {
+ path, title string
+ want []string
+ }{
+ {"/signin", "Sign in · Slides", []string{
+ "Email address",
+ "We’ll email you a link that signs you in. It works once and expires in 15 minutes.",
+ ">Sign in</button>",
+ }},
+ {"/signin?sent=1", "Check your email · Slides", []string{
+ "Check your email",
+ "If that address can sign in here, we’ve sent it a link. It works once and expires in 15 minutes.",
+ ">Use a different address</a>",
+ }},
+ {"/signed-out", "Signed out · Slides", []string{
+ "You’re signed out",
+ "You’ve signed out of Slides on this device.",
+ `href="/signin"`,
+ }},
+ } {
+ rec := get(t, h, tc.path)
+ body := rec.Body.String()
+ if rec.Code != http.StatusOK {
+ t.Fatalf("GET %s: %d", tc.path, rec.Code)
+ }
+ if m := title.FindStringSubmatch(body); m == nil || m[1] != tc.title {
+ t.Errorf("GET %s: title %v, want %q", tc.path, m, tc.title)
+ }
+ for _, w := range tc.want {
+ if !strings.Contains(body, w) {
+ t.Errorf("GET %s lacks %q", tc.path, w)
+ }
+ }
+ for _, bad := range []string{"Email me a link", "Home"} {
+ if strings.Contains(body, bad) {
+ t.Errorf("GET %s says %q", tc.path, bad)
+ }
+ }
+ }
+}