One thought shared once, and cards that read as cards - #215
Merged
Conversation
Three things found on one screen, /thoughts.
THE DUPLICATE. Not a rendering fault: production really held two sessions with
the same topic, prepared four seconds apart and shared thirteen seconds apart.
Each raised its own standing-search alert against the same person, so the
corpus, the alerts and every count on the home screen were wrong together, and
the page honestly showed the duplicate twice.
The conflict is subtler than it looks, and the obvious fix is wrong. There is
a UNIQUE constraint on `sessions.thought_id` that exists to prevent exactly
this, and it never fires, because the id ends in `secrets.token_hex(4)`. But
that randomness is not carelessness: the store never rebinds a `thought_id`,
even after deletion (v0.1 tombstone policy), so an id derived from the content
would permanently bar a person from sharing again anything they had once
withdrawn. I tried it and it does; the test that proves re-sharing still works
is in this commit for the next person who has the same idea.
So the id stays unique per attempt, with the reason written next to it, and
duplication is answered where it belongs -- as a product question, against
what is LIVE now. `_refuse_a_live_duplicate` compares the reasoning itself,
over labels rather than node ids (an assistant may supply ids one time and not
the next) and unordered (the order nodes arrive in is not the reasoning), and
refuses with a sentence that names the thought already here. Withdraw it and
you can share it again; another person may reason the same way freely.
THE CARDS. `.card-grid` items stretched to the tallest card in the row, and
`.card__actions { margin-top: auto }` then pushed the footer to the bottom of
that borrowed height -- so a short thought showed its title floating in the
middle of an empty card beside a long one. `align-items: start`. Measured
after: two cards, same top, heights 323 and 864.
THE STRUCTURE. Every idea was printed twice -- once as a chip and again inside
each relation it appears in -- so a cause with three effects appeared four
times and the card read as a heap. Relations are now grouped under the idea
they come from, which is also how a person says it: this causes that, and
that, and prevents the other. The cause takes the serif the page gives its
titles, the effects stay sans and quieter, and a hairline separates the
blocks, so a stack of chains no longer reads as one undifferentiated run.
Ideas nobody linked to anything are listed after, so they do not vanish.
705 tests OK, both gates pass with gold unedited.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three things found on one screen,
/thoughts.The duplicate — and why the obvious fix is wrong
Not a rendering fault. Production really held two sessions with the same topic, prepared four seconds apart and shared thirteen seconds apart. Each raised its own standing-search alert against the same person, so the corpus, the alerts and every count on the home screen were wrong together.
There is a UNIQUE constraint on
sessions.thought_idfor exactly this, and it never fires — the id ends insecrets.token_hex(4).But that randomness is not carelessness. The store never rebinds a
thought_id, even after deletion (v0.1 tombstone policy), so a content-derived id would permanently bar someone from sharing again anything they had once withdrawn. I tried it, and it does. The test proving re-sharing still works is in this commit for the next person who has the same idea.So the id stays unique per attempt — with the reason written beside it — and duplication is answered where it belongs: as a product question, against what is live now.
_refuse_a_live_duplicatecompares the reasoning itself — over labels rather than node ids (an assistant may supply ids one time and not the next), unordered (arrival order is not the reasoning) — and refuses with a sentence naming the thought already here:Withdraw it and you can share it again. Another person may reason the same way freely.
The cards
Grid items stretched to the tallest card in the row, and
.card__actions { margin-top: auto }pushed the footer to the bottom of that borrowed height — so a short thought showed its title floating in the middle of an empty card.align-items: start. Measured after: two cards, same top, heights 323 and 864.The structure
Every idea was printed twice — as a chip, and again inside each relation it appears in — so a cause with three effects appeared four times and the card read as a heap.
Relations are now grouped under the idea they come from, which is how a person says it: this causes that, and that, and prevents the other. The cause takes the serif the page gives its titles, effects stay sans and quieter, a hairline separates the blocks. Ideas nobody linked are listed after so they do not vanish.
🤖 Generated with Claude Code