Find logo and Refresh logo in a dialog on the company's page, not on a page of their own #674

Closed
opened 2026-10-03 01:58:52 +00:00 by tiagoagueda · 0 comments
Owner

From the list: “When capturing a logo, don't take user to a new page. Use a dialog instead.” Pressing Find logo or Refresh logo on a company's page sends the person to a page that only shows a spinner and then a link back.

What exists today

  • Two buttons post to the server and leave the page. company_detail.html draws Find logo (a company with a website and no logo) and Refresh logo (one with a remembered source address) as small forms posting to jobs:company_logo_action (src/postulo/templates/jobs/company_detail.html:48-58). The view hands the work to the errand layer and redirects to core:errand (src/postulo/jobs/views.py:437-468, the redirect at :468); Remove logo is instant and stays on the page (:457-460).
  • That page is a card with one sentence. core/errand.html shows the work's name and the state, polled every two seconds by the fragment core/partials/errand_state.html; on success it offers “Take me to it” (src/postulo/templates/core/errand.html, src/postulo/templates/core/partials/errand_state.html, src/postulo/core/views_errands.py:41-51). The state fragment is already ownership-checked and already degrades without a script (a “Check again” link).
  • The work is find_a_logo, registered as the logo errand: it reads the company's site and tries up to six images, or fetches the remembered address again (src/postulo/jobs/slow.py:130-157). An instance with no worker does the work in the request and answers “done”, with the same page (src/postulo/core/errands.py).
  • A dialog component exists and was built for this kind of question. <c-dialog> is a <dialog popover> that is a popover with no script and modal with one, takes a live form that htmx posts, and has a tone="plain" variant for a dialog that holds something to do (src/postulo/templates/cotton/dialog.html, src/postulo/static/js/app.js:1798-1862, #303). It is used today on Your details for removing a row (src/postulo/templates/accounts/profile.html:231) and tested with and without scripts (tests/e2e/test_dialog.py).
  • A failure is only as good as its message. What the errand reports on failure is whatever find_on_website raises, which today drops the reasons it collected (#526, open); the reason a site blocked or served nothing usable is lost either way.
  • The company form's own logo address and upload fields work on the form and do not leave the page (src/postulo/jobs/forms.py:291-314); this issue does not touch them.
  • Tests that cover the buttons: tests/test_logos.py::test_find_logo_and_refresh_are_only_ever_pressed_by_a_person (:393) and test_logo_actions_are_private_to_the_owner (:430), the isolation sweep (tests/security/test_isolation_sweep.py:384), tests/test_slow_requests.py:38.

Checked against main at f09b6010b.

Proposal

  1. A dialog on the company's page replaces the two forms: the button is a popovertarget button, the dialog is <c-dialog tone="plain"> containing the same POST form. It says what will happen in the person's own terms: “Postulo will read example.com and keep the first image that works as the logo” (for Refresh: “Postulo will fetch the logo again from ”), then offers Find logo beside Cancel. With no script it is a popover and the form posts as it does now.
  2. One view, two answers. CompanyLogoActionView.post answers a plain POST as today (redirect to core:errand) and an htmx POST with the errand-state fragment, so the dialog's body becomes the progress line and then the result. This is the shape accounts/views.py already uses for the row removal (request.htmx).
  3. The fragment reads right inside a dialog. Its text says “You can leave this page”; in the dialog it says “You can close this. The work carries on without you.” It ends with Done rather than “Take me to it”, and a failure shows the reason and a way to try again or to paste an address or upload a file instead (the edit form, with a link).
  4. When it succeeds the page shows the new logo without the person pressing anything more: Done (or closing the dialog) refreshes the company's header region, and the header's logo tag is the thing that changes.
  5. Focus and announcement. Focus moves to the result line when the state changes (it is already a polite live region), returns to the button that opened the dialog on close, and the dialog is labelled by its heading and described by what it will do (aria-labelledby, aria-describedby, which the component already sets).
  6. No new fetching. The same errand, the same guarded client, the same limits (#407 is the open issue on those limits); only where the answer is shown changes.

Decisions

  • One press or two. The dialog could start the work the moment it opens (one press), or open first, say what will be fetched, and wait for a second press. Recommended: two. A dialog opened by popovertarget does not make a request, so one press needs more script than the rest of the interface has; and Postulo's rule is that fetching is “only ever pressed by a person” (tests/test_logos.py:393), with the host named before it happens, which a second press makes visible.
  • What goes in the dialog. Find and Refresh only, or also the address and upload fields from the company form. Recommended: only the two that leave the page. The form's fields do not navigate anywhere; moving them is a separate redesign of the company form.
  • Closing while it works. The errand carries on and the page shows the logo the next time it is drawn; or the dialog refuses to close. Recommended: carry on, which is what the errand page already promises, and say so in the dialog.
  • Other errands (PDF, export, capture) still land on the errand page. Recommended: leave them; the fragment and the one-view-two-answers shape are what they would reuse if the maintainer wants the same for them.
  • Dependency on #526. The dialog is only as helpful as the failure sentence. Recommended: do not block on #526, but do the two in the same release so the dialog does not show “nothing could be used” without a reason.

Builds on

Issues #21 (finding a logo), #247 (the errand and its “working…” state), #303 (the dialog component and its script), #526 (the failures never shown). Related: #525 and #355 (the logo file's lifecycle), #407 (limits on person-chosen fetches).

Done when

  • In tests/test_logos.py: an htmx POST to jobs:company_logo_action answers 200 with the errand state and no redirect; a plain POST still redirects to core:errand; both still 404 for another owner (extend test_logo_actions_are_private_to_the_owner).
  • A browser test beside tests/e2e/test_dialog.py: the dialog opens from Find logo, is modal with scripts and a popover without, shows progress then the result, gives focus back, and passes axe in both themes; a failure shows its reason.
  • tests/test_slow_requests.py:38 still holds (the action does not run inside the request's transaction), and the page's entries in tests/test_page_coverage.py and tests/security/test_isolation_sweep.py are unchanged because no address is added.
  • No record type is added: the archive format (29 at f09b6010b) and the candidate format (4) stay as they are. New strings are in the working catalogues (en-GB, fr-FR, pt-PT as drafts) and Weblate (#349); the wiki's company page (Tracking applications) says where the logo is found.

From the list of improvements of 2026-10-02.

From the list: “When capturing a logo, don't take user to a new page. Use a dialog instead.” Pressing *Find logo* or *Refresh logo* on a company's page sends the person to a page that only shows a spinner and then a link back. ## What exists today - **Two buttons post to the server and leave the page.** `company_detail.html` draws *Find logo* (a company with a website and no logo) and *Refresh logo* (one with a remembered source address) as small forms posting to `jobs:company_logo_action` (`src/postulo/templates/jobs/company_detail.html:48-58`). The view hands the work to the errand layer and redirects to `core:errand` (`src/postulo/jobs/views.py:437-468`, the redirect at `:468`); *Remove logo* is instant and stays on the page (`:457-460`). - **That page is a card with one sentence.** `core/errand.html` shows the work's name and the state, polled every two seconds by the fragment `core/partials/errand_state.html`; on success it offers “Take me to it” (`src/postulo/templates/core/errand.html`, `src/postulo/templates/core/partials/errand_state.html`, `src/postulo/core/views_errands.py:41-51`). The state fragment is already ownership-checked and already degrades without a script (a “Check again” link). - **The work is `find_a_logo`**, registered as the `logo` errand: it reads the company's site and tries up to six images, or fetches the remembered address again (`src/postulo/jobs/slow.py:130-157`). An instance with no worker does the work in the request and answers “done”, with the same page (`src/postulo/core/errands.py`). - **A dialog component exists and was built for this kind of question.** `<c-dialog>` is a `<dialog popover>` that is a popover with no script and modal with one, takes a `live` form that htmx posts, and has a `tone="plain"` variant for a dialog that holds something to do (`src/postulo/templates/cotton/dialog.html`, `src/postulo/static/js/app.js:1798-1862`, #303). It is used today on *Your details* for removing a row (`src/postulo/templates/accounts/profile.html:231`) and tested with and without scripts (`tests/e2e/test_dialog.py`). - **A failure is only as good as its message.** What the errand reports on failure is whatever `find_on_website` raises, which today drops the reasons it collected (#526, open); the reason a site blocked or served nothing usable is lost either way. - The company form's own logo address and upload fields work on the form and do not leave the page (`src/postulo/jobs/forms.py:291-314`); this issue does not touch them. - Tests that cover the buttons: `tests/test_logos.py::test_find_logo_and_refresh_are_only_ever_pressed_by_a_person` (`:393`) and `test_logo_actions_are_private_to_the_owner` (`:430`), the isolation sweep (`tests/security/test_isolation_sweep.py:384`), `tests/test_slow_requests.py:38`. Checked against `main` at `f09b6010b`. ## Proposal 1. **A dialog on the company's page** replaces the two forms: the button is a `popovertarget` button, the dialog is `<c-dialog tone="plain">` containing the same POST form. It says what will happen in the person's own terms: “Postulo will read example.com and keep the first image that works as the logo” (for *Refresh*: “Postulo will fetch the logo again from <address>”), then offers *Find logo* beside *Cancel*. With no script it is a popover and the form posts as it does now. 2. **One view, two answers.** `CompanyLogoActionView.post` answers a plain POST as today (redirect to `core:errand`) and an htmx POST with the errand-state fragment, so the dialog's body becomes the progress line and then the result. This is the shape `accounts/views.py` already uses for the row removal (`request.htmx`). 3. **The fragment reads right inside a dialog.** Its text says “You can leave this page”; in the dialog it says “You can close this. The work carries on without you.” It ends with *Done* rather than “Take me to it”, and a failure shows the reason and a way to try again or to paste an address or upload a file instead (the edit form, with a link). 4. **When it succeeds the page shows the new logo** without the person pressing anything more: *Done* (or closing the dialog) refreshes the company's header region, and the header's logo tag is the thing that changes. 5. **Focus and announcement.** Focus moves to the result line when the state changes (it is already a polite live region), returns to the button that opened the dialog on close, and the dialog is labelled by its heading and described by what it will do (`aria-labelledby`, `aria-describedby`, which the component already sets). 6. **No new fetching.** The same errand, the same guarded client, the same limits (#407 is the open issue on those limits); only where the answer is shown changes. ## Decisions - **One press or two.** The dialog could start the work the moment it opens (one press), or open first, say what will be fetched, and wait for a second press. **Recommended: two.** A dialog opened by `popovertarget` does not make a request, so one press needs more script than the rest of the interface has; and Postulo's rule is that fetching is “only ever pressed by a person” (`tests/test_logos.py:393`), with the host named before it happens, which a second press makes visible. - **What goes in the dialog.** *Find* and *Refresh* only, or also the address and upload fields from the company form. **Recommended: only the two that leave the page.** The form's fields do not navigate anywhere; moving them is a separate redesign of the company form. - **Closing while it works.** The errand carries on and the page shows the logo the next time it is drawn; or the dialog refuses to close. **Recommended: carry on**, which is what the errand page already promises, and say so in the dialog. - **Other errands** (PDF, export, capture) still land on the errand page. **Recommended: leave them**; the fragment and the one-view-two-answers shape are what they would reuse if the maintainer wants the same for them. - **Dependency on #526.** The dialog is only as helpful as the failure sentence. **Recommended: do not block on #526**, but do the two in the same release so the dialog does not show “nothing could be used” without a reason. ## Builds on Issues #21 (finding a logo), #247 (the errand and its “working…” state), #303 (the dialog component and its script), #526 (the failures never shown). Related: #525 and #355 (the logo file's lifecycle), #407 (limits on person-chosen fetches). ## Done when - In `tests/test_logos.py`: an htmx POST to `jobs:company_logo_action` answers 200 with the errand state and no redirect; a plain POST still redirects to `core:errand`; both still 404 for another owner (extend `test_logo_actions_are_private_to_the_owner`). - A browser test beside `tests/e2e/test_dialog.py`: the dialog opens from *Find logo*, is modal with scripts and a popover without, shows progress then the result, gives focus back, and passes axe in both themes; a failure shows its reason. - `tests/test_slow_requests.py:38` still holds (the action does not run inside the request's transaction), and the page's entries in `tests/test_page_coverage.py` and `tests/security/test_isolation_sweep.py` are unchanged because no address is added. - No record type is added: the archive format (29 at `f09b6010b`) and the candidate format (4) stay as they are. New strings are in the working catalogues (en-GB, fr-FR, pt-PT as drafts) and Weblate (#349); the wiki's company page (*Tracking applications*) says where the logo is found. From the list of improvements of 2026-10-02.
tiagoagueda added this to the 0.6.0 milestone 2026-10-03 01:58:52 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#674
No description provided.