Repository navigation
fix: Talk Page Disclaimer - #7815
Merged
Merged
Conversation
shaohuzhang1
commented
Nov 10, 2026
| </footer> | ||
| </div> | ||
| </main> | ||
| </template> |
Contributor
Author
There was a problem hiding this comment.
Review
1. Correctness / Type safety
disclaimerText computed is fragile
const disclaimerText = computed(() => (list.appInfo.value?.disclaimer ? list.appInfo.value.disclaimer_value : ''))- If
disclaimeris truthy butdisclaimer_valueisnull/undefined, the computed returnsundefined(type becomesstring | undefined), not''. Add?? ''. - Backend flags are often
0/1or"true"; truthiness may not match intent. Normalize explicitly if the API is not strictly boolean. - Whitespace-only values will pass
v-if. Trim. - Optional-chaining is used on
.disclaimerbut not on.disclaimer_value— TS narrowing happens to cover this today, but it's brittle if the expression is refactored.
Suggested:
const disclaimerText = computed(() => {
const info = list.appInfo.value
return info?.disclaimer ? (info.disclaimer_value ?? '').trim() : ''
})Duplicated business rule — the comment says this must match the “display settings preview”. That rule now lives in two places and will drift. Extract it into a shared helper/composable (e.g. useDisclaimerText() / getDisclaimerText(appInfo)) and reuse it in the preview component.
2. Template / store access
list.appInfo.value?.…andlist.currentChatId.valueare used in the template. That only works iflistis a plain object holding refs. Iflistis ever converted to a Pinia/composable store with auto-unwrapped refs, all of these silently break (.valuebecomesundefined). Consider consistently exposing plain computed values instead, e.g.const backgroundImage = computed(...).:stylerunsJSON.stringify(...)on every render. Cheap, but acomputedis cleaner and gives you one place to sanitize.JSON.stringifyis not a CSS sanitizer.url(${JSON.stringify(url)})will produce invalid CSS for URLs containing newlines/CRs (JSON escapes them to\n, which CSS interprets as a literaln). Prefer validating/encoding:Also consider that aconst backgroundImage = computed(() => { const url = list.appInfo.value?.chat_background return url ? `url("${encodeURI(url).replace(/"/g, '%22')}")` : undefined })
background-imagevalue coming from server data is a mild CSS-injection surface; restricting tohttp(s):/data:imageis safer.<MessageList ...></MessageList>can be self-closed for brevity.
3. Layout / UX
- Verify
flex-col-centerexists in your utility layer. If it doesn't (or resolves todisplay:flex; align-items:center; justify-content:centerwithoutflex-direction: column), the input and disclaimer will sit side by side. - In a column,
justify-centeron the footer would center the input vertically — check thatflex-col-centerdoesn't unintentionally add vertical centering now that the footer has two children. text-N500over a user-providedchat_backgroundimage may have poor contrast. Consider a subtle backdrop (bg-surface/60, a blur, or a text-shadow) for readability.-mb-1is a magic offset to counteractp-4; if the padding changes, the alignment breaks. Prefer adjusting the footer padding or using explicit spacing.- Consider
select-texton the<p>if the disclaimer contains links/legal text users may want to copy, androle="note"for semantics. {{ disclaimerText }}is auto-escaped — good. If the content is ever meant to be rich text (markdown/HTML), do not switch tov-htmlwithout sanitizing.- Note the disclaimer lives inside
<main @drop>, so dropping a file on the disclaimer also adds an attachment. Probably intended, but worth confirming.
4. Code style / minor
import { computed } from 'vue'— fine, unless the project usesunplugin-auto-import; then this import is redundant and may fail the lint rule. Check sibling components for consistency.- Slot reformatting is purely cosmetic — no issue.
- Long
:styleline exceeds typical prettier width; a computed would also fix readability. - Naming:
disclaimer_valueis snake_case (backend shape) mixed with camelCase locals. Consider mapping to camelCase at the store boundary.
Suggested revision
const disclaimerText = computed(() => {
const info = list.appInfo.value
return info?.disclaimer ? (info.disclaimer_value ?? '').trim() : ''
})
const backgroundImage = computed(() => {
const url = list.appInfo.value?.chat_background
return url ? `url("${encodeURI(url).replace(/"/g, '%22')}")` : undefined
})<footer v-if="!list.isShared.value" class="mk-conversation-content flex-col-center shrink-0 gap-2 p-4">
<MessageInput />
<p v-if="disclaimerText" role="note" class="wrap-anywhere text-center text-sm text-N500">
{{ disclaimerText }}
</p>
</footer>
shaohuzhang1
commented
Nov 10, 2026
| <p v-if="disclaimerText" class="wrap-anywhere text-center text-sm text-N500 -mb-1">{{ disclaimerText }}</p> | ||
| </footer> | ||
| </main> | ||
| </template> |
Contributor
Author
There was a problem hiding this comment.
Review
1. Logic / correctness
currentApplicationIdis used only as a truthiness gate – it doesn't actually select the disclaimer, yet it silently suppresses the text when the id is falsy (0,'',undefined). Either make the dependency explicit (e.g. gate onappInfo.id === currentApplicationId) or drop the check entirely. As written, it reads like a bug magnet.- Possible stale text when switching agents –
appInfomay still hold the previous agent's data during the transition, so users could briefly see another agent's disclaimer. Consider resetting/validating against the current agent, or reading the disclaimer from the same source that renders the current conversation. - Empty/whitespace
disclaimer_value– ifdisclaimeristruebut the value is""or" ", the<p>still renders (in the whitespace case)..trim()before returning. - Return type is
string | undefined, sincedisclaimer_valuemay be optional. It works withv-if, but if you want a cleanstringcontract, coalesce:This also avoids repeatedly pokingconst disclaimerText = computed(() => { const info = list.appInfo.value if (!portalList.currentApplicationId.value || !info?.disclaimer) return '' return info.disclaimer_value?.trim() ?? '' })
.valueand keeps the truthiness check shallow.
2. Template / CSS
flex-center+flex-col-centeron the same element is suspicious – the two utilities likely both setdisplay:flex/align-items, so the result depends on CSS order/specificity. Use onlyflex-col-center(orflex flex-col items-center gap-2) to avoid a silently order-dependent layout.- Input width regression risk –
flex-col-centerpresumably meansalign-items: center, so<MessageInput />may no longer stretch to full width the way it did under the row-orientedflex-center. Addw-full/self-stretchto<MessageInput />(oritems-stretchon the footer and only center the<p>), and verify on wide screens. -mb-1is a hack – negative margins combined with the parent'sp-4are hard to reason about and can clip/overflow when the footer participates in a flex layout withmin-h-0. Prefer adjusting padding (px-4 pt-4 pb-3) or usemt-1on the<p>.- Verify these class names exist/case-match your token system:
flex-col-center,wrap-anywhere(Tailwind hasbreak-words/[overflow-wrap:anywhere]),text-N500(tokens are often lowercase, e.g.text-n500). Typos here fail silently. - Multi-line disclaimers – if the legal text contains
\n, it will collapse. Addwhitespace-pre-lineif line breaks are expected. - Accessibility/semantics – consider
role="note"(oraria-live="polite"if the text can change while visible) on the<p>, andtext-balance/max-w-proseso long disclaimers don't span the full panel width awkwardly.
3. Readability / minor
- Extract a small
DisclaimerTextcomponent or at least an i18n key if the string ever needs localization (currently dynamic, so likely fine, but the surrounding patterns are worth checking). disclaimer_value(snake_case) vsdisclaimer(camelCase) is an API-shape inconsistency; if you have a mapper layer, normalize it there rather than in the component.- The comment is helpful; consider noting why
currentApplicationIdis part of the condition (that's the non-obvious part).
Suggested version
const disclaimerText = computed(() => {
const info = list.appInfo.value
if (!portalList.currentApplicationId.value || !info?.disclaimer) return ''
return info.disclaimer_value?.trim() ?? ''
})<footer
v-if="!list.isShared.value"
class="mk-conversation-content flex-col-center shrink-0 gap-2 px-4 pt-4 pb-3"
>
<MessageInput class="w-full" />
<p
v-if="disclaimerText"
class="wrap-anywhere text-center text-sm text-N500 whitespace-pre-line"
role="note"
>
{{ disclaimerText }}
</p>
</footer>Main risks to verify before merging: the flex-center/flex-col-center combination, MessageInput losing full width, and the existence of the wrap-anywhere / text-N500 classes.
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.
fix: Talk Page Disclaimer