@open-mercato/core 0.6.2-develop.3446.1.bd060c6017 → 0.6.2-develop.3467.1.2a1818709d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/api/users/route.js +3 -6
  3. package/dist/modules/auth/api/users/route.js.map +2 -2
  4. package/dist/modules/auth/backend/users/[id]/edit/page.js +2 -1
  5. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  6. package/dist/modules/auth/backend/users/create/page.js +2 -1
  7. package/dist/modules/auth/backend/users/create/page.js.map +2 -2
  8. package/dist/modules/auth/commands/users.js +5 -8
  9. package/dist/modules/auth/commands/users.js.map +2 -2
  10. package/dist/modules/auth/data/entities.js.map +1 -1
  11. package/dist/modules/auth/frontend/login.js +35 -0
  12. package/dist/modules/auth/frontend/login.js.map +2 -2
  13. package/dist/modules/auth/lib/displayName.js +10 -0
  14. package/dist/modules/auth/lib/displayName.js.map +7 -0
  15. package/dist/modules/business_rules/lib/rule-evaluator.js +3 -3
  16. package/dist/modules/business_rules/lib/rule-evaluator.js.map +2 -2
  17. package/dist/modules/messages/components/MessagesInboxPageClient.js +4 -2
  18. package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
  19. package/dist/modules/messages/components/defaults/DefaultMessageListItem.js +8 -6
  20. package/dist/modules/messages/components/defaults/DefaultMessageListItem.js.map +2 -2
  21. package/dist/modules/messages/components/messageListLabels.js +14 -0
  22. package/dist/modules/messages/components/messageListLabels.js.map +7 -0
  23. package/dist/modules/messages/components/useMessagesInboxBulkActions.js +47 -23
  24. package/dist/modules/messages/components/useMessagesInboxBulkActions.js.map +2 -2
  25. package/package.json +7 -7
  26. package/src/modules/auth/api/users/route.ts +3 -6
  27. package/src/modules/auth/backend/users/[id]/edit/page.tsx +2 -1
  28. package/src/modules/auth/backend/users/create/page.tsx +2 -1
  29. package/src/modules/auth/commands/users.ts +5 -8
  30. package/src/modules/auth/data/entities.ts +1 -1
  31. package/src/modules/auth/frontend/login.tsx +40 -0
  32. package/src/modules/auth/lib/displayName.ts +6 -0
  33. package/src/modules/business_rules/lib/rule-evaluator.ts +3 -3
  34. package/src/modules/messages/components/MessagesInboxPageClient.tsx +5 -2
  35. package/src/modules/messages/components/defaults/DefaultMessageListItem.tsx +9 -5
  36. package/src/modules/messages/components/messageListLabels.ts +29 -0
  37. package/src/modules/messages/components/useMessagesInboxBulkActions.ts +58 -26
  38. package/src/modules/messages/i18n/de.json +2 -0
  39. package/src/modules/messages/i18n/en.json +2 -0
  40. package/src/modules/messages/i18n/es.json +2 -0
  41. package/src/modules/messages/i18n/pl.json +2 -0
@@ -17,6 +17,7 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
17
17
  import { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'
18
18
  import { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'
19
19
  import { UserConsentsPanel } from '@open-mercato/core/modules/auth/components/UserConsentsPanel'
20
+ import { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'
20
21
 
21
22
  type EditUserFormValues = {
22
23
  email: string
@@ -442,7 +443,7 @@ export default function EditUserPage({ params }: { params?: { id?: string } }) {
442
443
  const payload = {
443
444
  id: id ? String(id) : '',
444
445
  email: values.email,
445
- name: typeof values.name === 'string' && values.name.trim().length ? values.name.trim() : undefined,
446
+ name: normalizeDisplayNameInput(values.name),
446
447
  password: values.password && values.password.trim() ? values.password : undefined,
447
448
  organizationId: values.organizationId ? values.organizationId : undefined,
448
449
  roles: Array.isArray(values.roles) ? values.roles : [],
@@ -14,6 +14,7 @@ import { RadioGroup } from '@open-mercato/ui/primitives/radio'
14
14
  import { RadioField } from '@open-mercato/ui/primitives/radio-field'
15
15
  import { useT } from '@open-mercato/shared/lib/i18n/context'
16
16
  import { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'
17
+ import { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'
17
18
 
18
19
  type CreateUserFormValues = {
19
20
  email: string
@@ -326,7 +327,7 @@ export default function CreateUserPage() {
326
327
  const customFields = collectCustomFieldValues(values)
327
328
  const payload: Record<string, unknown> = {
328
329
  email: values.email,
329
- name: typeof values.name === 'string' && values.name.trim().length ? values.name.trim() : undefined,
330
+ name: normalizeDisplayNameInput(values.name),
330
331
  organizationId: values.organizationId ? values.organizationId : null,
331
332
  roles: Array.isArray(values.roles) ? values.roles : [],
332
333
  ...(Object.keys(customFields).length ? { customFields } : {}),
@@ -37,6 +37,7 @@ import InviteUserEmail from '@open-mercato/core/modules/auth/emails/InviteUserEm
37
37
  import { INVITE_TOKEN_TTL_MS } from '@open-mercato/core/modules/auth/lib/inviteToken'
38
38
  import { getSecurityEmailBaseUrl } from '@open-mercato/shared/lib/url'
39
39
  import { generateAuthToken, hashAuthToken } from '@open-mercato/core/modules/auth/lib/tokenHash'
40
+ import { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'
40
41
 
41
42
  type SerializedUser = {
42
43
  email: string
@@ -76,12 +77,8 @@ type UserSnapshots = {
76
77
  const passwordSchema = buildPasswordSchema()
77
78
 
78
79
  const displayNameSchema = z.preprocess(
79
- (value) => {
80
- if (typeof value !== 'string') return value
81
- const trimmed = value.trim()
82
- return trimmed.length ? trimmed : undefined
83
- },
84
- z.string().trim().min(1).max(120).optional(),
80
+ normalizeDisplayNameInput,
81
+ z.string().trim().min(1).max(120).nullable().optional(),
85
82
  )
86
83
 
87
84
  const createSchema = z.object({
@@ -589,7 +586,7 @@ const updateUserCommand: CommandHandler<Record<string, unknown>, User> = {
589
586
  entity.organizationId = before.organizationId ?? null
590
587
  entity.tenantId = before.tenantId ?? null
591
588
  entity.passwordHash = before.passwordHash ?? null
592
- entity.name = before.name ?? undefined
589
+ entity.name = before.name ?? null
593
590
  entity.isConfirmed = before.isConfirmed
594
591
  },
595
592
  })
@@ -715,7 +712,7 @@ const deleteUserCommand: CommandHandler<{ body?: Record<string, unknown>; query?
715
712
  user.organizationId = before.organizationId ?? null
716
713
  user.tenantId = before.tenantId ?? null
717
714
  user.passwordHash = before.passwordHash ?? null
718
- user.name = before.name ?? undefined
715
+ user.name = before.name ?? null
719
716
  user.isConfirmed = before.isConfirmed
720
717
  await em.flush()
721
718
  } else {
@@ -19,7 +19,7 @@ export class User {
19
19
  emailHash?: string | null
20
20
 
21
21
  @Property({ type: 'text', nullable: true })
22
- name?: string
22
+ name?: string | null
23
23
 
24
24
  @Property({ name: 'password_hash', type: 'text', nullable: true })
25
25
  passwordHash?: string | null
@@ -13,6 +13,7 @@ import { Button } from '@open-mercato/ui/primitives/button'
13
13
  import { useT } from '@open-mercato/shared/lib/i18n/context'
14
14
  import { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'
15
15
  import { clearAllOperations } from '@open-mercato/ui/backend/operations/store'
16
+ import { notifyAuthIdentityChange } from '@open-mercato/ui/backend/AuthSessionGuard'
16
17
  import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
17
18
  import { X } from 'lucide-react'
18
19
  import { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'
@@ -125,6 +126,43 @@ export default function LoginPage() {
125
126
  setClientReady(true)
126
127
  }, [])
127
128
 
129
+ useEffect(() => {
130
+ let cancelled = false
131
+ void (async () => {
132
+ try {
133
+ const res = await apiCall<{ userId?: string }>('/api/auth/feature-check', {
134
+ method: 'POST',
135
+ headers: { 'content-type': 'application/json' },
136
+ body: JSON.stringify({ features: [] }),
137
+ cache: 'no-store',
138
+ })
139
+ if (cancelled) return
140
+ const activeUserId = typeof res.result?.userId === 'string' ? res.result.userId : ''
141
+ if (!activeUserId) return
142
+ const rawRedirect = searchParams.get('redirect') || ''
143
+ let destination = '/backend'
144
+ if (rawRedirect) {
145
+ try {
146
+ const resolved = new URL(rawRedirect, window.location.origin)
147
+ if (
148
+ resolved.origin === window.location.origin &&
149
+ resolved.pathname.startsWith('/') &&
150
+ !resolved.pathname.includes('//')
151
+ ) {
152
+ destination = resolved.pathname + resolved.search + resolved.hash
153
+ }
154
+ } catch {
155
+ // fall back to /backend
156
+ }
157
+ }
158
+ router.replace(destination)
159
+ } catch {
160
+ // ignore — leave login form usable on network failure
161
+ }
162
+ })()
163
+ return () => { cancelled = true }
164
+ }, [router, searchParams])
165
+
128
166
  useEffect(() => {
129
167
  const tenantParam = (searchParams.get('tenant') || '').trim()
130
168
  if (tenantParam) {
@@ -212,6 +250,7 @@ export default function LoginPage() {
212
250
  const res = await fetch('/api/auth/login', { method: 'POST', body: form })
213
251
  if (res.redirected) {
214
252
  clearAllOperations()
253
+ notifyAuthIdentityChange()
215
254
  // NextResponse.redirect from API
216
255
  router.replace(res.url)
217
256
  return
@@ -265,6 +304,7 @@ export default function LoginPage() {
265
304
  const data = await res.json().catch(() => null) as LoginResponseEventDetail
266
305
  emitLoginResponseEvent(data)
267
306
  clearAllOperations()
307
+ notifyAuthIdentityChange()
268
308
  if (data && typeof data.redirect === 'string' && data.redirect.length > 0) {
269
309
  router.replace(data.redirect)
270
310
  }
@@ -0,0 +1,6 @@
1
+ export function normalizeDisplayNameInput(value: unknown): unknown {
2
+ if (value === null) return null
3
+ if (typeof value !== 'string') return value
4
+ const trimmed = value.trim()
5
+ return trimmed.length > 0 ? trimmed : null
6
+ }
@@ -77,7 +77,7 @@ export async function evaluate(
77
77
  }
78
78
  }
79
79
 
80
- const evaluationTime = Date.now() - startTime
80
+ const evaluationTime = Math.max(0, Date.now() - startTime)
81
81
 
82
82
  // Determine if conditions were met (at least one rule matched)
83
83
  const conditionsPassed = matchedRules.length > 0
@@ -140,7 +140,7 @@ export async function evaluateSingleRule(
140
140
  // Evaluate conditions
141
141
  const conditionsPassed = await evaluateConditions(rule.conditionExpression, data, context)
142
142
 
143
- const evaluationTime = Date.now() - startTime
143
+ const evaluationTime = Math.max(0, Date.now() - startTime)
144
144
 
145
145
  console.log('[RULE EVAL] Final result:', {
146
146
  ruleId: rule.ruleId,
@@ -156,7 +156,7 @@ export async function evaluateSingleRule(
156
156
  evaluationTime,
157
157
  }
158
158
  } catch (error) {
159
- const evaluationTime = Date.now() - startTime
159
+ const evaluationTime = Math.max(0, Date.now() - startTime)
160
160
  const errorMessage = error instanceof Error ? error.message : String(error)
161
161
 
162
162
  console.log('[RULE EVAL] ✗ ERROR:', errorMessage)
@@ -16,6 +16,7 @@ import { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'
16
16
  import { Archive, ChevronDown, FilePenLine, Inbox, Layers, Send } from 'lucide-react'
17
17
  import { getMessageUiComponentRegistry } from './utils/typeUiRegistry'
18
18
  import { DefaultMessageListItem } from './defaults/DefaultMessageListItem'
19
+ import { getMessageListParticipantLabel } from './messageListLabels'
19
20
  import { toErrorMessage } from './message-detail/utils'
20
21
  import { useMessagesInboxBulkActions, type MessageFolder } from './useMessagesInboxBulkActions'
21
22
 
@@ -33,6 +34,7 @@ type MessageListItem = {
33
34
  objectCount: number
34
35
  hasAttachments: boolean
35
36
  attachmentCount: number
37
+ recipientCount?: number
36
38
  hasActions: boolean
37
39
  actionTaken?: string | null
38
40
  sentAt?: string | null
@@ -319,11 +321,12 @@ export function MessagesInboxPageClient() {
319
321
  bodyFormat: 'text' as const,
320
322
  priority: (item.priority as 'low' | 'normal' | 'high' | 'urgent') ?? 'normal',
321
323
  sentAt: item.sentAt ? new Date(item.sentAt) : null,
322
- senderName: item.senderName || item.senderEmail || item.senderUserId,
324
+ senderName: getMessageListParticipantLabel(item, folder, t),
323
325
  hasObjects: item.hasObjects,
324
326
  objectCount: item.objectCount,
325
327
  hasAttachments: item.hasAttachments,
326
328
  attachmentCount: item.attachmentCount,
329
+ recipientCount: item.recipientCount ?? 0,
327
330
  hasActions: item.hasActions,
328
331
  actionTaken: item.actionTaken ?? null,
329
332
  unread: item.status === 'unread',
@@ -333,7 +336,7 @@ export function MessagesInboxPageClient() {
333
336
  )
334
337
  },
335
338
  },
336
- ], [listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t])
339
+ ], [folder, listItemComponentKeyByType, messageTypeLabelMap, messageUiRegistry, router, t])
337
340
 
338
341
  const folderOptions = React.useMemo(() => [
339
342
  { id: 'inbox' as const, label: t('messages.folder.inbox', 'Inbox'), icon: Inbox },
@@ -31,7 +31,7 @@ function formatSentTime(value: Date | null): string {
31
31
 
32
32
  function truncateWords(value: string, maxWords: number): string {
33
33
  const normalized = value.trim().replace(/\s+/g, ' ')
34
- if (!normalized) return '—'
34
+ if (!normalized) return ''
35
35
  const words = normalized.split(' ')
36
36
  if (words.length <= maxWords) return normalized
37
37
  return `${words.slice(0, maxWords).join(' ')}...`
@@ -39,8 +39,8 @@ function truncateWords(value: string, maxWords: number): string {
39
39
 
40
40
  export function DefaultMessageListItem({ message, onClick }: MessageListItemProps) {
41
41
  const t = useT()
42
- const senderLabel = message.senderName || '—'
43
- const subject = message.subject || '—'
42
+ const senderLabel = message.senderName?.trim() || t('messages.list.noRecipient', '(No recipient)')
43
+ const subject = message.subject.trim() || t('messages.list.noSubject', '(No subject)')
44
44
  const absoluteSentAt = formatDateTime(message.sentAt)
45
45
  const sentAtLabel = formatSentTime(message.sentAt)
46
46
  const bodyPreview = truncateWords(message.body || '', 16)
@@ -75,8 +75,12 @@ export function DefaultMessageListItem({ message, onClick }: MessageListItemProp
75
75
  <span className={cn('truncate text-foreground', message.unread ? 'font-semibold' : 'font-normal')}>
76
76
  {subject}
77
77
  </span>
78
- <span className="text-muted-foreground">-</span>
79
- <span className="truncate text-muted-foreground">{bodyPreview}</span>
78
+ {bodyPreview ? (
79
+ <>
80
+ <span className="text-muted-foreground">-</span>
81
+ <span className="truncate text-muted-foreground">{bodyPreview}</span>
82
+ </>
83
+ ) : null}
80
84
  </div>
81
85
 
82
86
  <div className="flex flex-shrink-0 items-center gap-1.5 text-muted-foreground">
@@ -0,0 +1,29 @@
1
+ import type { MessageFolder } from './useMessagesInboxBulkActions'
2
+
3
+ type Translate = (key: string, fallback: string, params?: Record<string, string | number>) => string
4
+
5
+ type MessageParticipantSource = {
6
+ senderName?: string | null
7
+ senderEmail?: string | null
8
+ senderUserId: string
9
+ recipientCount?: number | null
10
+ }
11
+
12
+ function normalizeLabel(value: string | null | undefined): string | null {
13
+ const normalized = value?.trim()
14
+ return normalized ? normalized : null
15
+ }
16
+
17
+ export function getMessageListParticipantLabel(
18
+ item: MessageParticipantSource,
19
+ folder: MessageFolder,
20
+ t: Translate,
21
+ ): string {
22
+ if ((folder === 'sent' || folder === 'drafts') && Number(item.recipientCount ?? 0) <= 0) {
23
+ return t('messages.list.noRecipient', '(No recipient)')
24
+ }
25
+
26
+ return normalizeLabel(item.senderName)
27
+ ?? normalizeLabel(item.senderEmail)
28
+ ?? item.senderUserId
29
+ }
@@ -13,7 +13,7 @@ import { toErrorMessage } from './message-detail/utils'
13
13
 
14
14
  export type MessageFolder = 'inbox' | 'sent' | 'drafts' | 'archived' | 'all'
15
15
 
16
- type MessageBulkActionId = 'markRead' | 'markUnread' | 'archive' | 'delete'
16
+ export type MessageBulkActionId = 'markRead' | 'markUnread' | 'archive' | 'delete'
17
17
 
18
18
  type BulkExecutionSummary = {
19
19
  action: MessageBulkActionId
@@ -31,6 +31,13 @@ type MessageBulkRequestConfig = {
31
31
  errorFallback: string
32
32
  }
33
33
 
34
+ type MessageBulkActionDefinition = {
35
+ id: string
36
+ labelKey: string
37
+ labelFallback: string
38
+ destructive?: boolean
39
+ }
40
+
34
41
  type MessageInboxBulkMutationContext = {
35
42
  actionId: MessageBulkActionId
36
43
  messageIds: string[]
@@ -87,6 +94,42 @@ const MESSAGE_BULK_REQUESTS: Record<MessageBulkActionId, MessageBulkRequestConfi
87
94
  },
88
95
  }
89
96
 
97
+ const MESSAGE_BULK_ACTIONS: Record<MessageBulkActionId, MessageBulkActionDefinition> = {
98
+ markRead: {
99
+ id: 'messages-mark-read',
100
+ labelKey: 'messages.actions.markRead',
101
+ labelFallback: 'Mark read',
102
+ },
103
+ markUnread: {
104
+ id: 'messages-mark-unread',
105
+ labelKey: 'messages.actions.markUnread',
106
+ labelFallback: 'Mark unread',
107
+ },
108
+ archive: {
109
+ id: 'messages-archive',
110
+ labelKey: 'messages.actions.archive',
111
+ labelFallback: 'Archive',
112
+ },
113
+ delete: {
114
+ id: 'messages-delete',
115
+ labelKey: 'messages.actions.delete',
116
+ labelFallback: 'Delete',
117
+ destructive: true,
118
+ },
119
+ }
120
+
121
+ const MESSAGE_BULK_ACTION_IDS_BY_FOLDER = {
122
+ inbox: ['markRead', 'markUnread', 'archive', 'delete'],
123
+ archived: ['markRead', 'markUnread', 'delete'],
124
+ sent: ['delete'],
125
+ drafts: ['delete'],
126
+ all: [],
127
+ } satisfies Record<MessageFolder, readonly MessageBulkActionId[]>
128
+
129
+ export function getMessageBulkActionIdsForFolder(folder: MessageFolder): MessageBulkActionId[] {
130
+ return [...MESSAGE_BULK_ACTION_IDS_BY_FOLDER[folder]]
131
+ }
132
+
90
133
  function normalizeSelectionScopeValue(value: unknown): unknown {
91
134
  if (value == null) return undefined
92
135
  if (typeof value === 'string') {
@@ -287,31 +330,20 @@ export function useMessagesInboxBulkActions<T extends MessageInboxBulkRow>({
287
330
  }, [confirm, filterValues, folder, page, queryClient, retryLastMutation, runMutation, search, t])
288
331
 
289
332
  const bulkActions = React.useMemo<BulkAction<T>[] | undefined>(
290
- () => folder === 'inbox'
291
- ? [
292
- {
293
- id: 'messages-mark-read',
294
- label: t('messages.actions.markRead', 'Mark read'),
295
- onExecute: (selectedRows: T[]) => executeBulkAction('markRead', selectedRows),
296
- },
297
- {
298
- id: 'messages-mark-unread',
299
- label: t('messages.actions.markUnread', 'Mark unread'),
300
- onExecute: (selectedRows: T[]) => executeBulkAction('markUnread', selectedRows),
301
- },
302
- {
303
- id: 'messages-archive',
304
- label: t('messages.actions.archive', 'Archive'),
305
- onExecute: (selectedRows: T[]) => executeBulkAction('archive', selectedRows),
306
- },
307
- {
308
- id: 'messages-delete',
309
- label: t('messages.actions.delete', 'Delete'),
310
- destructive: true,
311
- onExecute: (selectedRows: T[]) => executeBulkAction('delete', selectedRows),
312
- },
313
- ]
314
- : undefined,
333
+ () => {
334
+ const actionIds = getMessageBulkActionIdsForFolder(folder)
335
+ if (actionIds.length === 0) return undefined
336
+
337
+ return actionIds.map((actionId) => {
338
+ const action = MESSAGE_BULK_ACTIONS[actionId]
339
+ return {
340
+ id: action.id,
341
+ label: t(action.labelKey, action.labelFallback),
342
+ destructive: action.destructive,
343
+ onExecute: (selectedRows: T[]) => executeBulkAction(actionId, selectedRows),
344
+ }
345
+ })
346
+ },
315
347
  [executeBulkAction, folder, t],
316
348
  )
317
349
 
@@ -159,6 +159,8 @@
159
159
  "messages.forwardNote": "Optionale Notiz",
160
160
  "messages.includeAttachments": "Anhänge einschließen",
161
161
  "messages.includeAttachmentsHint": "Anhänge aus der Originalnachricht übernehmen.",
162
+ "messages.list.noRecipient": "(Kein Empfänger)",
163
+ "messages.list.noSubject": "(Kein Betreff)",
162
164
  "messages.loading.attachments": "Anhänge werden geladen...",
163
165
  "messages.loading.detail": "Nachricht wird geladen...",
164
166
  "messages.loading.objectTypes": "Objekttypen werden geladen...",
@@ -159,6 +159,8 @@
159
159
  "messages.forwardNote": "Optional note",
160
160
  "messages.includeAttachments": "Include attachments",
161
161
  "messages.includeAttachmentsHint": "Carry over attachments from the original message.",
162
+ "messages.list.noRecipient": "(No recipient)",
163
+ "messages.list.noSubject": "(No subject)",
162
164
  "messages.loading.attachments": "Loading attachments...",
163
165
  "messages.loading.detail": "Loading message...",
164
166
  "messages.loading.objectTypes": "Loading object types...",
@@ -159,6 +159,8 @@
159
159
  "messages.forwardNote": "Nota opcional",
160
160
  "messages.includeAttachments": "Incluir adjuntos",
161
161
  "messages.includeAttachmentsHint": "Conservar adjuntos del mensaje original.",
162
+ "messages.list.noRecipient": "(Sin destinatario)",
163
+ "messages.list.noSubject": "(Sin asunto)",
162
164
  "messages.loading.attachments": "Cargando adjuntos...",
163
165
  "messages.loading.detail": "Cargando mensaje...",
164
166
  "messages.loading.objectTypes": "Cargando tipos de objeto...",
@@ -159,6 +159,8 @@
159
159
  "messages.forwardNote": "Notatka opcjonalna",
160
160
  "messages.includeAttachments": "Dołącz załączniki",
161
161
  "messages.includeAttachmentsHint": "Przenieś załączniki z oryginalnej wiadomości.",
162
+ "messages.list.noRecipient": "(Brak odbiorcy)",
163
+ "messages.list.noSubject": "(Brak tematu)",
162
164
  "messages.loading.attachments": "Ładowanie załączników...",
163
165
  "messages.loading.detail": "Ładowanie wiadomości...",
164
166
  "messages.loading.objectTypes": "Ładowanie typów obiektów...",