# Technical feasibility prototype

Status: ready to implement as a separate prototype. Created 2026-09-30. No implementation or device verification is claimed by this document.

## Objective

Demonstrate that an anonymous visitor can take a quiz, share a saved result through a mobile share action, and receive compatibility results from recipients when returning in the same browser.

Use bare minimum HTML and a small persistent service. Visual design belongs to the independent [design exploration](design-exploration-spec.md). Product constraints are in [AGENTS.md](../AGENTS.md); author content is in [content_reference.md](content_reference.md).

The prototype answers these feasibility questions:

- Does an invitation retain the sender's exact saved result across browsers and quiz steps?
- Can recipients see personal and compatibility results, while senders later see multiple anonymous completions?
- Can a phone share the invitation natively, with usable alternatives where native sharing is unavailable?
- Can Telegram fetch a Russian preview of the saved result from a publicly reachable URL?
- What access is lost when an anonymous owner changes browser or clears browser state?

## Scope

| Included | Deferred |
| --- | --- |
| Anonymous browser ownership | Accounts, names, login, cross-device recovery |
| Two sample questions with four options each | Complete production quiz and final tie policy |
| Eight stable outcome identifiers | Full expanded outcome presentation |
| Persistent immutable results and invitations | Administrative content editor |
| Compatibility from final outcome pairs | Author-approved compatibility matrix |
| Recipient result and sender completion list | Notifications, bots, live presence |
| Native URL sharing, Telegram link, copy fallback | Image download and file sharing |
| Server-rendered result preview metadata | Generated album artwork and final styling |
| Simple analysis transition and album-link slot | Music playback and polished animation |

Use plain semantic HTML, basic readable CSS, and only the JavaScript required for flow and browser capabilities. Touch controls should remain comfortable on a phone. A framework is optional; it must not expand the slice into a production architecture project.

## User flow

### Sender A

1. Open `/` and choose `Пройти диагностику`.
2. Answer the two sample questions. Show `Вопрос 1 из 2` and `Вопрос 2 из 2`.
3. Submit the completed quiz. The service calculates and persists one result once.
4. Show `Анализ сигнала` briefly, then the personal result. The album-link area appears during this transition.
5. Prepare a public invitation URL for that immutable result. Show `Поделиться результатом`, `Отправить в Telegram`, and `Скопировать ссылку` as appropriate to browser support.
6. Leave the site. Returning to `/` or `/my-results` in the same browser gives access to saved results and anonymous compatibility completions.

### Recipient B

1. Open `/s/{shareId}` in an independent browser session.
2. See the sender's outcome and `Пройдите диагностику и сравните ваши сигналы`. The sender is anonymous.
3. Start the quiz. Retain the invitation through all steps and reloads.
4. Submit once. Persist B's result and the comparison with A's saved result.
5. Show B's personal result and `Совместимость с человеком, который отправил ссылку`.
6. Offer a new invitation for B's result. B does not inherit A's identity or public invitation.

### Returning sender A

1. Return in the original browser and open `Мои сигналы`.
2. See each saved result and its completion list.
3. A list entry shows `Сигнал 1`, the recipient's outcome, and compatibility. Include a completion time if useful, without suggesting it identifies the person.
4. Show `Пока никто не завершил диагностику по вашей ссылке` when there are no completions.
5. Use an explicit `Обновить` action to fetch new completions. Background polling is unnecessary.

### Further recipient C

C opens B's new invitation, completes the quiz, and compares with B. This completion appears for B, without appearing as a direct completion of A's invitation.

## Anonymous ownership

Use a first-party, opaque anonymous session credential in a persistent cookie. The service maps it to an owner record. Prefer an HttpOnly cookie with Secure on HTTPS and SameSite=Lax. This is access control without a user account; do not describe it as verified identity.

The default prototype session lifetime is 30 days, a reversible implementation assumption. Save completed results server-side and retain them across service restarts. Use a small persistent database, such as SQLite with persistent storage, or a similarly simple managed store. An in-memory map does not satisfy sender return.

Public share IDs and private ownership credentials must be different. A public invitation grants access to the sender's shared outcome and the quiz entry point. It must not grant access to the sender's completion list, session credential, or raw answers. Use unguessable public IDs and owner credentials generated with a cryptographically secure source.

The first slice intentionally accepts these limits:

- Another browser, another device, or cleared/expired cookies can lose access to the owner's private result list.
- A result created in Telegram's embedded browser may not be accessible from an external browser through the same anonymous session. Measure this instead of promising continuity.
- A public link can be forwarded. Its recipient still compares with the result attached to that link.
- Anonymous completion entries cannot reliably identify real people. Repeated runs can produce multiple entries.

Use concise explanatory copy beside the return feature, for example `Ваши результаты доступны в этом браузере. Если очистить его данные, доступ может потеряться.` Do not introduce a registration step to fix these accepted limits.

## Persistence contract

| Record | Required fields and rules |
| --- | --- |
| Anonymous owner | Internal ID, session credential hash, creation and expiry times |
| Quiz attempt | Owner ID, quiz version, answers or minimal scoring state, optional incoming share ID, draft/completed state |
| Result | ID, owner ID, attempt ID, outcome ID, scoring version, creation time; immutable once completed |
| Invitation | Public share ID, result ID, creation time; always references one immutable result |
| Completion | Incoming share ID, recipient result ID, compatibility key, compatibility version, completion time |

Keep answer drafts separate from public preview data. Only retain raw answers where necessary to resume or verify scoring; neither previews nor sender completion lists need them.

Completion submission must be idempotent for one attempt. Enforce a unique result per completed attempt and one incoming completion per attempt. A double tap, retry after timeout, or refresh must not create duplicate completion entries.

Calculate the result and its incoming completion in one transaction. If saving fails, preserve the user's answers and offer retry. Do not reveal a success state that has not been saved.

Retaking the quiz starts a new attempt. Previously shared links keep their original result. There is one reusable invitation per result in the first slice, so several recipients can complete it.

If the invitation's saved result belongs to the current owner, allow viewing and sharing that result. Suppress a self-comparison entry in the sender's list. Document this as a prototype policy.

## Route and service behavior

The following is an implementation contract; HTML form handlers can replace JSON endpoints if they preserve the same behavior.

| Route | Behavior |
| --- | --- |
| `GET /` | Entry screen and link to same-browser saved results |
| `GET /s/{shareId}` | Public invitation HTML with the sender outcome and result-specific preview metadata |
| `POST /api/attempts` | Start an attempt with an optional validated incoming invitation |
| `POST /api/attempts/{id}/answers` | Save allowed answers for the current owner; preserve incoming invitation |
| `POST /api/attempts/{id}/complete` | Validate all answers, score, persist result and optional incoming completion, return the saved result and invitation |
| `GET /results/{resultId}` | Owner's personal result and incoming comparison if present |
| `GET /my-results` | Owner's results and completion lists; no public history access |
| `GET /api/my-results` | Optional refresh response for the same owner |
| `GET /preview/{outcomeId}.png` | Public deterministic preview image for an allowed outcome |

Public link GET requests must be side-effect free. A preview crawler opening the link must not create an attempt, a completion, or a new anonymous quiz owner. Start an attempt only after the person chooses to begin.

Reject invalid answers, unknown outcomes, unavailable invitations, and attempts owned by another session. Display understandable Russian recovery text. An invalid invitation offers `Пройти тест без сравнения`; require the user to choose this before dropping comparison context.

Use a single origin for page routes and the service to simplify cookie ownership. Keep owner credentials out of public URLs, previews, share payloads, client-readable logs, and screenshots.

## Sample quiz and scoring

Use `СИГНАЛ` and `РАССТОЯНИЕ`, the first two questions in the canonical reference, with their four options and corresponding scoring rows. Sum answer weights for all eight outcomes. The highest score wins.

Use the following stable IDs in the reference's outcome order:

| ID | Public outcome title |
| --- | --- |
| `supernova` | ПРЕЖДЕВРЕМЕННАЯ СВЕРХНОВАЯ |
| `lost-signal` | ПОТЕРЯННЫЙ СИГНАЛ |
| `distant-light` | ДАЛЕКИЙ СВЕТ |
| `network-search` | ВЕЧНЫЙ ПОИСК СЕТИ |
| `unknown-object` | НЕОПОЗНАННЫЙ ЭМОЦИОНАЛЬНЫЙ ОБЪЕКТ |
| `earth-object` | ЗЕМНОЙ ОБЪЕКТ |
| `invisible` | РЕЖИМ НЕВИДИМКИ |
| `instability` | КРИТИЧЕСКАЯ НЕСТАБИЛЬНОСТЬ |

For the two-question fixture quiz, break ties by this order. Record `sample-two-question-v1` as the scoring version and show a concise `Демонстрационная версия` label. This deterministic fallback is a feasibility fixture, not the author's final seven-question tie policy.

Answers A+B produce `earth-object`. Answers B+A produce a tie between `distant-light` and `unknown-object`, resolved to `distant-light` by the fixture order. These paths provide repeatable acceptance examples.

Tests may score synthetic weight fixtures to cover all eight identifiers. Any developer-only result override must be visibly separate from the participant flow. Final scoring integration requires all seven questions and a documented policy for every maximum-score tie.

## Provisional compatibility rules

The author reference contains no compatibility matrix. The user authorizes plausible filler. Use this explicit, symmetric fixture matrix for the prototype. It depends only on the two outcome IDs and includes same-outcome pairs.

| Code | Russian title | Provisional description |
| --- | --- | --- |
| O | На одной орбите | Ваши сигналы находят друг друга. Поддерживайте связь и иногда возвращайтесь на Землю. |
| P | Притяжение на расстоянии | Между вами есть притяжение, даже если сигналы идут с задержкой. Попробуйте сказать прямо, чего ждёте. |
| T | Нужна настройка частоты | Вы передаёте на разных частотах. Немного ясности поможет услышать друг друга. |
| N | Сигналы с помехами | Одному нужен ответ, другому хочется исчезнуть с радаров. Начните с короткого честного сообщения. |

| Outcome | supernova | lost-signal | distant-light | network-search | unknown-object | earth-object | invisible | instability |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| supernova | T | T | P | N | T | O | N | N |
| lost-signal | T | P | P | T | T | O | N | T |
| distant-light | P | P | O | P | P | O | N | T |
| network-search | N | T | P | T | T | O | N | N |
| unknown-object | T | T | P | T | T | P | T | T |
| earth-object | O | O | O | O | P | O | P | T |
| invisible | N | N | N | N | T | P | T | N |
| instability | N | T | T | N | T | T | N | N |

Use version `compatibility-fixture-v1`. Display `Пример совместимости` on prototype comparison views and keep the table and descriptions in replaceable content data. Do not invent percentages or imply this is a scientific relationship assessment. The creative diagnosis framing comes from the author.

Store the compatibility key and version when a completion is created so later content changes do not silently reclassify existing entries. A later author-approved matrix may use different symmetry or descriptions and needs an explicit version change.

## Native sharing and fallbacks

The primary action shares only the public invitation URL, a Russian title, and short Russian invitation text. Prepare the URL before the person taps the share button. Invoke `navigator.share` directly from that gesture; do not fetch or generate the invitation first inside the tap handler and risk losing user activation. Web Share requires a secure context and user activation; browser policy and the available targets also affect the operation. These constraints come from the [Web Share API specification](https://www.w3.org/TR/web-share/).

Suggested share text: `Мой сигнал: Далёкий свет. Пройди диагностику и узнай, как сочетаются наши сигналы.` Substitute the saved outcome title.

Detect capability at runtime. Where available, validate the URL payload using `navigator.canShare`. Offer a Telegram sharing URL with correctly encoded `url` and `text`, following [Telegram's sharing button documentation](https://core.telegram.org/widgets/share). Provide `Скопировать ссылку` and a selectable URL if clipboard access fails.

Treat share cancellation as an ordinary return to the result page. Keep fallbacks available when sharing fails. A resolved share promise confirms handoff, not that another person received the message or completed the quiz. The [Web Share specification](https://www.w3.org/TR/web-share/) defines this handoff behavior. Do not show a fictitious recipient or completion count after sharing.

## Public result preview

Return result-specific metadata in the initial HTML response for `/s/{shareId}`. Do not depend on JavaScript to add it. Include Russian `og:title`, `og:description`, `og:image:alt`, and `og:locale=ru_RU`, plus `og:type=website`, the invitation's absolute `og:url`, and an absolute public `og:image` URL. The basic metadata fields follow the [Open Graph protocol](https://ogp.me/).

The metadata and visible invitation page must refer to the same saved result. `og:url` must retain the invitation identifier rather than pointing to the generic home page. Use simple deterministic PNG artwork or a type card for this technical slice. A 1200 by 630 pixel preview is an implementation starting point, not a guaranteed Telegram display contract.

The HTML and image must be publicly retrievable without the owner's cookie. Keep URLs stable and results immutable to avoid previews changing after a retake. Measure actual Telegram rendering and caching during the phone pass; metadata correctness alone does not prove a preview appears.

Testing external previews requires a public HTTPS URL and persistent storage. Localhost is enough for automated route tests, but it does not establish Telegram's ability to fetch the page. Choose a minimal public test deployment when implementing the slice and record its URL and storage behavior.

## Theatrical analysis and promotional link

Persist the result before starting the theatrical transition. Show `Анализ сигнала`, then reveal the result after about three seconds, a provisional duration. Include `Показать результат` to skip the delay. The display is part of the album's fiction; it must not conceal a genuine network error or imply unfinished persistence has succeeded.

Provide a configurable album URL. The final destination is unresolved because the discussion mentioned Bandcamp and the reference mentions band.link. Use `Послушать альбом` when a real destination is supplied. If absent, show `Ссылка на альбом появится позже` in the prototype and document the missing input. Do not invent an artist URL.

Opening the album link must not lose the saved result. Album promotion never gates completion. Music is outside this technical proof.

## Acceptance and evidence

User correction on 2026-10-01: the mobile quiz flow is viewport-only. Intro, both questions, analysis, personal result and recipient comparison must not scroll, horizontally or vertically. Questions show the prompt, all options and primary action together. Long result copy uses the user-approved additional fixed Next/Back steps. Sharing and comparison use separate fixed panels. Do not hide required content below a clipped container. Test 320×568 and 390×844 at minimum, including Russian recovery messages. The sender history can use fixed paginated entries rather than a growing scroll list. The design comparison gallery is outside the quiz flow.

Start all browser acceptance work in a phone-sized portrait viewport, with 390 by 844 CSS pixels as the baseline and a 320 pixel width check. Use isolated browser contexts for A, B, and C. Device emulation is evidence for browser flow and layout, not native sharing.

| Check | Required evidence |
| --- | --- |
| A completes sample quiz | Expected persisted outcome, Russian result page, one reusable invitation |
| B opens A's link in a fresh context | A's saved outcome appears; invitation survives steps and refresh |
| B completes | B sees their personal result and the correct pair-matrix compatibility |
| A returns and refreshes | B's anonymous completion appears exactly once |
| A's invitation gets another completion | Both entries appear without overwriting the first |
| B shares with C | C compares with B; the entry appears for B and is not a direct A completion |
| Sender retakes | Old invitation and preview retain the old outcome; new result gets a new invitation |
| Repeated submit or retry | One saved result and one incoming completion for the attempt |
| Service restarts | Saved invitations, results, and lists remain available |
| Cookie is cleared | Private list becomes inaccessible to the new session; public invitation still works |
| Public visitor requests owner result/history | No private list or owner credential is disclosed |
| Preview crawler opens link | Correct metadata and image, no attempt or completion created |
| Unsupported native sharing or cancellation | Result remains usable; fallback link and copy path work |
| Invalid invitation or failed save | Russian recovery, preserved answers where relevant, no fabricated comparison |

Unit checks should cover score totals, deterministic ties, all 64 ordered compatibility lookups, matrix symmetry for this fixture, and duplicate-submission handling. End-to-end tests should verify the actual A to B to A and B to C journeys. Do not add tests that merely repeat static copy or CSS values.

Perform a real-phone pass on iOS Safari and Android Chrome when the devices are available. Open the native share chooser, select Telegram, inspect the link preview, open the link as a recipient, complete it, and return as sender. Also check Telegram's embedded browser on those devices. Record browser and Telegram versions, test date, device, observed behavior, and any browser-session discontinuity.

If a device or live Telegram pass is unavailable, deliver the working prototype with that check marked unverified. Report the feasibility conclusion as partial until the missing integration evidence exists. Never claim an emulator opened the OS share sheet.

Check Russian text, keyboard completion, visible focus, touch access, readable controls, and no document or inner-panel scrolling in either direction. Inspect visible errors and foreground collisions, not just scroll extents. Record enlarged-text limitations separately. Desktop checks are supplementary.

## Implementation sequence and deliverables

1. Choose the smallest runnable service and persistent store. Create two-question scoring and versioned compatibility fixtures.
2. Implement owner sessions, attempts, immutable results, invitations, and idempotent completions.
3. Build the minimal A, B, and returning-A pages, including the further-sharing chain.
4. Add server-rendered previews, native sharing, and fallbacks.
5. Run focused scoring, persistence, ownership, and mobile flow tests.
6. Make a public HTTPS test environment available and collect real-phone evidence where possible.

Keep the slice in a separate directory such as `prototypes/technical/`. Delivery includes runnable source, run commands, persistent-store setup, sample content/version files, a test report, the public test URL if established, and a short finding on anonymous access and phone sharing limitations.

Any iterative test runner, fixture enumeration, deployment helper, or other long-running script must report completed/total work and the current operation. Progress reporting is a project instruction, not optional polish.

Completion means the documented round trips work with persistent storage and the evidence identifies exactly which phone integrations were verified. The prototype remains intentionally minimal and does not select a final visual style.
