The hint at the foot of Message headers named an action and left you to go find it. The reporter is right that it is the shape of the thing rather than the size — telling somebody a feature exists is half a job when the other half is one element away.
Clicking it now closes the headers dialog and opens the original, so it reads as going deeper rather than as opening a second window.
tNode, not a chopped sentence
tNode("Use {action} for the complete raw message.", { action: <button/> }) rather than two translate calls either side of a button. The sentence stays whole for whoever translates it, and a language that puts the verb somewhere else can move the hole instead of being handed two fragments. That is what tNode is there for and the reasoning is already written at its definition.
A bug found on the way
The link style needed unscoping to work, and that turned out to be a defect of its own.
.link-btn was written for the composer's To/Cc labels and scoped to .composer-field label. Two callers live outside that scope — the trusted-domain list in PrivacySettings.tsx:221, and now this one — so both rendered as default button chrome in the middle of a sentence. The rule is now unscoped, which fixes Privacy by the same change.
Worth flagging as a visual change beyond the issue: those trusted-domain buttons will now look like links, which is what the class name always claimed they were.
Verified in a browser
Against the mock: opened a message, More → Show headers, confirmed the hint renders as one sentence with a dotted-underlined link in it and no button background, clicked it, and landed on Original message with the raw source — one dialog open, not two.
npm run typecheck, npm test (992 web + 126 server), npm run build pass. i18n:check back to the same six pre-existing stale keys: the old string is retired and the interpolated one replaces it in all nine catalogues.
Closes #236.
The hint at the foot of **Message headers** named an action and left you to go find it. The reporter is right that it is the shape of the thing rather than the size — telling somebody a feature exists is half a job when the other half is one element away.
Clicking it now closes the headers dialog and opens the original, so it reads as going *deeper* rather than as opening a second window.
## `tNode`, not a chopped sentence
`tNode("Use {action} for the complete raw message.", { action: <button/> })` rather than two `translate` calls either side of a button. The sentence stays whole for whoever translates it, and a language that puts the verb somewhere else can move the hole instead of being handed two fragments. That is what `tNode` is there for and the reasoning is already written at its definition.
## A bug found on the way
The link style needed unscoping to work, and that turned out to be a defect of its own.
`.link-btn` was written for the composer's To/Cc labels and scoped to `.composer-field label`. Two callers live outside that scope — the trusted-domain list in `PrivacySettings.tsx:221`, and now this one — so both rendered as **default button chrome in the middle of a sentence**. The rule is now unscoped, which fixes Privacy by the same change.
Worth flagging as a visual change beyond the issue: those trusted-domain buttons will now look like links, which is what the class name always claimed they were.
## Verified in a browser
Against the mock: opened a message, `More → Show headers`, confirmed the hint renders as one sentence with a dotted-underlined link in it and no button background, clicked it, and landed on **Original message** with the raw source — one dialog open, not two.
`npm run typecheck`, `npm test` (992 web + 126 server), `npm run build` pass. `i18n:check` back to the same six pre-existing stale keys: the old string is retired and the interpolated one replaces it in all nine catalogues.
**Merged** 2026-09-02 as coffey-labs/ihasmail@f6f6ce0b5eb7
<sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #236.
The hint at the foot of Message headers named an action and left you to go find it. The reporter is right that it is the shape of the thing rather than the size — telling somebody a feature exists is half a job when the other half is one element away.
Clicking it now closes the headers dialog and opens the original, so it reads as going deeper rather than as opening a second window.
tNode, not a chopped sentencetNode("Use {action} for the complete raw message.", { action: <button/> })rather than twotranslatecalls either side of a button. The sentence stays whole for whoever translates it, and a language that puts the verb somewhere else can move the hole instead of being handed two fragments. That is whattNodeis there for and the reasoning is already written at its definition.A bug found on the way
The link style needed unscoping to work, and that turned out to be a defect of its own.
.link-btnwas written for the composer's To/Cc labels and scoped to.composer-field label. Two callers live outside that scope — the trusted-domain list inPrivacySettings.tsx:221, and now this one — so both rendered as default button chrome in the middle of a sentence. The rule is now unscoped, which fixes Privacy by the same change.Worth flagging as a visual change beyond the issue: those trusted-domain buttons will now look like links, which is what the class name always claimed they were.
Verified in a browser
Against the mock: opened a message,
More → Show headers, confirmed the hint renders as one sentence with a dotted-underlined link in it and no button background, clicked it, and landed on Original message with the raw source — one dialog open, not two.npm run typecheck,npm test(992 web + 126 server),npm run buildpass.i18n:checkback to the same six pre-existing stale keys: the old string is retired and the interpolated one replaces it in all nine catalogues.Merged 2026-09-02 as coffey-labs/ihasmail@f6f6ce0b5e
Rebuilt from: git history, session transcript.