Skip to content

fix: Talk Page Disclaimer - #7815

Merged
shaohuzhang1 merged 1 commit into
v3from
pr@v3@fix_disclaimer
Nov 10, 2026
Merged

shaohuzhang1 merged 1 commit into
v3from
pr@v3@fix_disclaimer

Conversation

@shaohuzhang1

Copy link
Copy Markdown
Contributor

fix: Talk Page Disclaimer

</footer>
</div>
</main>
</template>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

1. Correctness / Type safety

disclaimerText computed is fragile

const disclaimerText = computed(() => (list.appInfo.value?.disclaimer ? list.appInfo.value.disclaimer_value : ''))
  • If disclaimer is truthy but disclaimer_value is null/undefined, the computed returns undefined (type becomes string | undefined), not ''. Add ?? ''.
  • Backend flags are often 0/1 or "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 .disclaimer but 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?.… and list.currentChatId.value are used in the template. That only works if list is a plain object holding refs. If list is ever converted to a Pinia/composable store with auto-unwrapped refs, all of these silently break (.value becomes undefined). Consider consistently exposing plain computed values instead, e.g. const backgroundImage = computed(...).
  • :style runs JSON.stringify(...) on every render. Cheap, but a computed is cleaner and gives you one place to sanitize.
  • JSON.stringify is 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 literal n). Prefer validating/encoding:
    const backgroundImage = computed(() => {
      const url = list.appInfo.value?.chat_background
      return url ? `url("${encodeURI(url).replace(/"/g, '%22')}")` : undefined
    })
    Also consider that a background-image value coming from server data is a mild CSS-injection surface; restricting to http(s):/data:image is safer.
  • <MessageList ...></MessageList> can be self-closed for brevity.

3. Layout / UX

  • Verify flex-col-center exists in your utility layer. If it doesn't (or resolves to display:flex; align-items:center; justify-content:center without flex-direction: column), the input and disclaimer will sit side by side.
  • In a column, justify-center on the footer would center the input vertically — check that flex-col-center doesn't unintentionally add vertical centering now that the footer has two children.
  • text-N500 over a user-provided chat_background image may have poor contrast. Consider a subtle backdrop (bg-surface/60, a blur, or a text-shadow) for readability.
  • -mb-1 is a magic offset to counteract p-4; if the padding changes, the alignment breaks. Prefer adjusting the footer padding or using explicit spacing.
  • Consider select-text on the <p> if the disclaimer contains links/legal text users may want to copy, and role="note" for semantics.
  • {{ disclaimerText }} is auto-escaped — good. If the content is ever meant to be rich text (markdown/HTML), do not switch to v-html without 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 uses unplugin-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 :style line exceeds typical prettier width; a computed would also fix readability.
  • Naming: disclaimer_value is 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
shaohuzhang1 merged commit dd1204c into v3 Nov 10, 2026
3 of 4 checks passed
@shaohuzhang1
shaohuzhang1 deleted the pr@v3@fix_disclaimer branch November 10, 2026 09:41
<p v-if="disclaimerText" class="wrap-anywhere text-center text-sm text-N500 -mb-1">{{ disclaimerText }}</p>
</footer>
</main>
</template>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

1. Logic / correctness

  • currentApplicationId is 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 on appInfo.id === currentApplicationId) or drop the check entirely. As written, it reads like a bug magnet.
  • Possible stale text when switching agents – appInfo may 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 – if disclaimer is true but the value is "" or " ", the <p> still renders (in the whitespace case). .trim() before returning.
  • Return type is string | undefined, since disclaimer_value may be optional. It works with v-if, but if you want a clean string contract, coalesce:
    const disclaimerText = computed(() => {
      const info = list.appInfo.value
      if (!portalList.currentApplicationId.value || !info?.disclaimer) return ''
      return info.disclaimer_value?.trim() ?? ''
    })
    This also avoids repeatedly poking .value and keeps the truthiness check shallow.

2. Template / CSS

  • flex-center + flex-col-center on the same element is suspicious – the two utilities likely both set display:flex/align-items, so the result depends on CSS order/specificity. Use only flex-col-center (or flex flex-col items-center gap-2) to avoid a silently order-dependent layout.
  • Input width regression risk – flex-col-center presumably means align-items: center, so <MessageInput /> may no longer stretch to full width the way it did under the row-oriented flex-center. Add w-full / self-stretch to <MessageInput /> (or items-stretch on the footer and only center the <p>), and verify on wide screens.
  • -mb-1 is a hack – negative margins combined with the parent's p-4 are hard to reason about and can clip/overflow when the footer participates in a flex layout with min-h-0. Prefer adjusting padding (px-4 pt-4 pb-3) or use mt-1 on the <p>.
  • Verify these class names exist/case-match your token system: flex-col-center, wrap-anywhere (Tailwind has break-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. Add whitespace-pre-line if line breaks are expected.
  • Accessibility/semantics – consider role="note" (or aria-live="polite" if the text can change while visible) on the <p>, and text-balance/max-w-prose so long disclaimers don't span the full panel width awkwardly.

3. Readability / minor

  • Extract a small DisclaimerText component 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) vs disclaimer (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 currentApplicationId is 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant