Skip to content

Feedback Domain (feedback_v1)

The Feedback domain (feedback_v1) is a bounded context that manages user feedback, issue reporting, and support communication. It handles the complete lifecycle of user-submitted feedback, including bug reports, feature requests, general feedback, and issues. This domain enables organizations to collect structured user input, track its progress through resolution workflows, and maintain a dialogue with users through response management.

The Feedback domain provides a comprehensive framework for feedback collection and management. It supports multiple feedback types and workflows, tracks feedback status through multiple states, manages responses and communication with users, and maintains rich metadata about each feedback item.

Key responsibilities:

  1. Feedback Lifecycle Management: Track feedback from submission through review, triage, response, and resolution
  2. Feedback Classification: Categorize feedback by type, priority, and category for effective routing
  3. Response Management: Support both public and internal (private) responses to user feedback
  4. Status Tracking: Monitor feedback progress through distinct workflow states
  5. Audit Trail & Metadata: Capture submission context including browser/device info and custom metadata
  6. Attachment Support: Associate files and attachments with feedback and responses
dart_packages/co2/domains/feedback_v1/
├── lib/
│ ├── feedback_v1.dart # Main export and registration
│ └── src/
│ ├── aggregates/
│ │ └── feedback_aggregate.dart # FeedbackAggregate root entity
│ ├── events/
│ │ └── feedback_events.dart # Domain events
│ └── directives/
│ └── feedback_directives.dart # Command handlers
├── pubspec.yaml
└── test/
└── feedback_v1_test.dart

The domain supports four primary feedback types through the FeedbackType value object:

TypeCodePurpose
Bug Reportbug_reportReports of functional defects or issues
Feature Requestfeature_requestRequests for new functionality
General Feedbackgeneral_feedbackComments and suggestions
IssueissueGeneral problem reports

Feedback can be assigned one of four priority levels using FeedbackPriority:

PriorityCodeImpact
LowlowNice-to-have improvements
MediummediumShould be addressed in normal workflow
HighhighSignificant impact, prioritize soon
UrgenturgentCritical issues requiring immediate attention

Feedback is organized into five categories using FeedbackCategory:

CategoryCodeDescription
UI/UXui_uxUser interface and experience issues
PerformanceperformanceSpeed, responsiveness, scalability concerns
FunctionalityfunctionalityFeature behavior and logic issues
DatadataData accuracy, integrity, or handling
OtherotherGeneral or uncategorized feedback

Feedback progresses through six distinct states:

┌─────────┐
│ new │──────────────────────┐
└────┬────┘ │
│ │
↓ ↓
┌──────────┐ ┌─────────┐
│ in_review│ │ rejected│
└────┬─────┘ └─────────┘
┌────────────┐
│in_progress │
└────┬───────┘
┌──────────┐
│ resolved │
└────┬─────┘
┌────────┐
│ closed │
└────────┘
  • new: Initial state when feedback is first submitted; ready for triage
  • in_review: Feedback is being assessed and categorized; may collect additional information
  • in_progress: Work is underway to address the feedback; at least one response has been provided
  • resolved: Issue or request has been addressed; user has received a solution or response
  • closed: Feedback lifecycle is complete; no further action expected
  • rejected: Feedback has been declined (e.g., duplicate, out of scope, not reproducible)
  1. Feedback title and description required: Both must be non-empty strings
  2. Valid status transitions: Only permitted transitions between states are allowed
  3. Valid feedback types: Only recognized type values are accepted
  4. Valid priorities and categories: Must match enumerated values
  5. Resolved feedback must have responses: Feedback cannot move to “resolved” status without at least one response (business rule enforced in validate())
  6. Context capture: Browser info and device info are captured for bug reports to aid reproduction

The root aggregate representing a single piece of user feedback with complete lifecycle management.

Identity & Core Properties:

final FeedbackId feedbackId; // Unique feedback identifier
final FeedbackTitle title; // User-visible title
final FeedbackDescription description; // Detailed description
final FeedbackType feedbackType; // Category: bug_report, feature_request, etc.
final FeedbackPriority priority; // Priority level: low, medium, high, urgent
final FeedbackCategory category; // Category: ui_ux, performance, etc.
final UserId submittedBy; // User who submitted the feedback
final DateTime submittedAt; // Submission timestamp
final DateTime updatedAt; // Last modification time
final FeedbackStatus status; // Current workflow state

Contact & Context Information:

final EmailAddress? contactEmail; // Optional contact email (can be different from submitter)
final BrowserInfo? browserInfo; // Browser version/type for bug context
final DeviceInfo? deviceInfo; // Device information (OS, model, etc.)
final List<AttachmentId> attachmentIds; // File attachments to the feedback
final Map<String, Object?> metadata; // Arbitrary custom key-value data

Responses:

final List<FeedbackResponse> _responses; // Internal collection of responses

Status & Lifecycle Queries:

  • isNew, isInReview, isInProgress, isResolved, isClosed, isRejected: Status checking
  • isOpen: True if feedback is still being worked on (not closed/resolved/rejected)
  • canBeResolved(): Check if feedback can transition to resolved (has responses)
  • canBeClosed(): Check if feedback can be closed (must be resolved or rejected)
  • canBeReopened(): Check if feedback can be reopened (was closed or resolved)

Response Management:

  • responses: Immutable list of all responses
  • publicResponses: List of responses visible to the user
  • privateResponses: List of internal/staff-only responses
  • hasResponses: Boolean check for response existence
  • hasPublicResponses: Check for user-visible responses
  • latestResponse: Get the most recent response by timestamp
  • responseCount: Total number of responses

Aging & Activity:

  • ageInDays: Days since feedback was submitted
  • isStale: True if older than 30 days and still open
  • daysSinceLastActivity: Days since last response (or submission if no responses)

Metadata & Analysis:

  • requiresUrgentAttention: True if urgent priority and still open
  • totalAttachmentCount: Count of attachments on feedback and all responses
  • metadata: Custom key-value data for extensibility

The aggregate applies events through the apply() method:

feedback = feedback.apply(FeedbackSubmittedEvent(
feedbackId: feedbackId,
title: FeedbackTitle('Login button unresponsive'),
description: FeedbackDescription('Cannot click login button on mobile'),
feedbackType: FeedbackType('bug_report'),
priority: FeedbackPriority('high'),
category: FeedbackCategory('ui_ux'),
submittedBy: userId,
submittedAt: DateTime.now(),
contactEmail: EmailAddress('user@example.com'),
browserInfo: BrowserInfo('Safari 17.1'),
deviceInfo: DeviceInfo('iPhone 14 Pro'),
attachmentIds: [attachmentId1],
metadata: {'screenResolution': '1170x2532'},
));

Represents a single response to feedback, which may be public (visible to user) or private (internal only).

class FeedbackResponse {
final FeedbackResponseId responseId; // Unique response identifier
final FeedbackResponseText responseText; // Response content
final UserId respondedBy; // Staff member or system
final DateTime respondedAt; // Response timestamp
final bool isPublic; // Whether visible to user
final List<AttachmentId> attachmentIds; // Files attached to response
}

Query Methods:

  • hasAttachments: Check if response includes files
  • ageInDays: Days since response was created
  • toJson(): Serialize to JSON
  • fromJson(): Deserialize from JSON

The domain publishes events capturing all significant state changes:

Fired when a user submits feedback (new → in_review or direct submission).

FeedbackSubmittedEvent(
feedbackId: FeedbackId.fromString('feedback-2024-001'),
title: FeedbackTitle('Export button missing in dashboard'),
description: FeedbackDescription(
'When viewing the analytics dashboard, I cannot find the export to CSV button '
'that was present in the previous version.'
),
feedbackType: FeedbackType('bug_report'),
priority: FeedbackPriority('medium'),
category: FeedbackCategory('functionality'),
submittedBy: UserId.fromString('user-alice'),
submittedAt: DateTime.now(),
contactEmail: EmailAddress('alice@example.com'),
browserInfo: BrowserInfo('Chrome 121.0'),
deviceInfo: DeviceInfo('Windows 10 Desktop'),
attachmentIds: [AttachmentId.fromString('screenshot-001')],
metadata: {
'url': '/analytics/dashboard',
'viewport': '1920x1080',
'timestamp': DateTime.now().toIso8601String(),
},
)

Payload:

  • feedbackId: Unique identifier
  • title: Brief summary
  • description: Detailed explanation
  • feedbackType: Classification (bug_report, feature_request, etc.)
  • priority: Priority level
  • category: Category classification
  • submittedBy: User identifier
  • submittedAt: Submission timestamp
  • contactEmail: Optional contact email
  • browserInfo: Optional browser context
  • deviceInfo: Optional device context
  • attachmentIds: Optional file attachments
  • metadata: Custom context data

Fired when feedback status transitions (e.g., in_review → in_progress).

FeedbackStatusChangedEvent(
feedbackId: FeedbackId.fromString('feedback-2024-001'),
previousStatus: FeedbackStatus('in_review'),
newStatus: FeedbackStatus('in_progress'),
changedBy: UserId.fromString('user-bob'),
changedAt: DateTime.now(),
response: 'Our team has confirmed the issue and is working on a fix.',
reason: 'Bug confirmed in development environment',
)

Payload:

  • feedbackId: Target feedback
  • previousStatus: Former state
  • newStatus: New state
  • changedBy: User making the change
  • changedAt: Timestamp of change
  • response: Optional public message to user
  • reason: Optional internal reason/notes

Fired when support staff responds to feedback.

FeedbackResponseAddedEvent(
feedbackId: FeedbackId.fromString('feedback-2024-001'),
responseId: FeedbackResponseId.fromString('response-001'),
responseText: FeedbackResponseText(
'We have identified the root cause of the export button issue. '
'A fix has been deployed to the staging environment and will be '
'released in our next production update scheduled for March 15th.'
),
respondedBy: UserId.fromString('support-team'),
respondedAt: DateTime.now(),
isPublic: true,
attachmentIds: [AttachmentId.fromString('release-notes-123')],
)

Payload:

  • feedbackId: Target feedback
  • responseId: Unique response identifier
  • responseText: Response content
  • respondedBy: Responder user ID
  • respondedAt: Response timestamp
  • isPublic: Whether visible to user
  • attachmentIds: Optional attachments (release notes, screenshots, etc.)

Directives are command handlers that validate inputs and produce events. The feedback domain implements directives for each major operation:

Creates and submits new feedback.

Payload:

class SubmitFeedbackPayload {
final FeedbackId feedbackId;
final FeedbackTitle title;
final FeedbackDescription description;
final FeedbackType feedbackType;
final FeedbackPriority priority;
final FeedbackCategory category;
final UserId submittedBy;
final EmailAddress? contactEmail;
final BrowserInfo? browserInfo;
final DeviceInfo? deviceInfo;
final List<AttachmentId> attachmentIds;
final Metadata metadata;
}

Output Events:

  • FeedbackSubmittedEvent with complete feedback details

Validation:

  • Title must not be empty
  • Description must not be empty
  • Feedback type must be valid
  • Priority must be valid
  • Category must be valid

Usage:

final payload = SubmitFeedbackPayload(
feedbackId: FeedbackId.fromString('feedback-2024-100'),
title: FeedbackTitle('Mobile app crashes on login'),
description: FeedbackDescription(
'The mobile app consistently crashes when attempting to log in with two-factor authentication.'
),
feedbackType: FeedbackType('bug_report'),
priority: FeedbackPriority('high'),
category: FeedbackCategory('functionality'),
submittedBy: UserId.fromString('user-charlie'),
contactEmail: EmailAddress('charlie@example.com'),
browserInfo: null,
deviceInfo: DeviceInfo('iOS 17.2 iPhone 15'),
attachmentIds: [crashLogId],
metadata: {
'appVersion': '2.1.0',
'buildNumber': '2024.001',
},
);
final directive = SubmitFeedbackDirective(payload: payload);

Changes feedback status and optionally adds a response message.

Payload:

class UpdateFeedbackStatusPayload {
final FeedbackId feedbackId;
final FeedbackStatus previousStatus;
final FeedbackStatus newStatus;
final UserId changedBy;
final String? response;
final String? reason;
}

Output Events:

  • FeedbackStatusChangedEvent with status transition details

Business Rule Enforcement:

  • New status must be valid
  • Status transition must follow workflow rules
  • Resolved status requires at least one response to exist

Usage:

final payload = UpdateFeedbackStatusPayload(
feedbackId: FeedbackId.fromString('feedback-2024-100'),
previousStatus: FeedbackStatus('in_review'),
newStatus: FeedbackStatus('in_progress'),
changedBy: UserId.fromString('user-bob'),
response: 'Our team has confirmed this issue and is working on a patch.',
reason: 'Bug reproduced in QA environment',
);
final directive = UpdateFeedbackStatusDirective(payload: payload);

Adds a response from support staff (public or internal).

Payload:

class AddFeedbackResponsePayload {
final FeedbackId feedbackId;
final FeedbackResponseId responseId;
final FeedbackResponseText responseText;
final UserId respondedBy;
final bool isPublic;
final List<AttachmentId> attachmentIds;
}

Output Events:

  • FeedbackResponseAddedEvent with response details

Validation:

  • Response text must not be empty
  • Response ID must be unique
  • User making response must be valid

Usage:

final payload = AddFeedbackResponsePayload(
feedbackId: FeedbackId.fromString('feedback-2024-100'),
responseId: FeedbackResponseId.fromString('response-100-001'),
responseText: FeedbackResponseText(
'Thank you for reporting this issue. We have deployed a hotfix in version 2.1.1 '
'which addresses the two-factor authentication crash. Please update and let us know '
'if the issue persists.'
),
respondedBy: UserId.fromString('support-engineer'),
isPublic: true,
attachmentIds: [releaseNotesId],
);
final directive = AddFeedbackResponseDirective(payload: payload);
import 'package:feedback_v1/feedback_v1.dart';
import 'package:contracts_v1/contracts_v1.dart';
// User submits a bug report with browser and device context
final reportPayload = SubmitFeedbackPayload(
feedbackId: FeedbackId.fromString('fb-2024-bug-001'),
title: FeedbackTitle('Sidebar navigation not scrolling'),
description: FeedbackDescription(
'When viewing a site with many sub-sections in the sidebar, the navigation '
'menu cannot be scrolled. This makes it impossible to access items below the fold.'
),
feedbackType: FeedbackType('bug_report'),
priority: FeedbackPriority('medium'),
category: FeedbackCategory('ui_ux'),
submittedBy: UserId.fromString('user-dave'),
contactEmail: EmailAddress('dave.smith@company.com'),
browserInfo: BrowserInfo('Firefox 123.0 on macOS Sonoma'),
deviceInfo: DeviceInfo('MacBook Pro 16-inch M3'),
attachmentIds: [screenshotId, videoId],
metadata: {
'viewportSize': '1440x900',
'screenResolution': '3456x2234',
'operatingSystem': 'macOS 14.2',
'timestamp': DateTime.now().toIso8601String(),
},
);
final directive = SubmitFeedbackDirective(payload: reportPayload);
// User submits a feature request
final featurePayload = SubmitFeedbackPayload(
feedbackId: FeedbackId.fromString('fb-2024-feature-001'),
title: FeedbackTitle('Bulk asset status update capability'),
description: FeedbackDescription(
'It would be helpful to select multiple assets and update their status '
'in one operation. Currently I have to update each asset individually, '
'which is time-consuming when managing hundreds of assets.'
),
feedbackType: FeedbackType('feature_request'),
priority: FeedbackPriority('high'),
category: FeedbackCategory('functionality'),
submittedBy: UserId.fromString('user-eve'),
contactEmail: null,
browserInfo: null,
deviceInfo: null,
attachmentIds: [],
metadata: {
'estimatedAssetCount': '500+',
'useFrequency': 'daily',
},
);
// After review, update status and provide feedback
final statusPayload = UpdateFeedbackStatusPayload(
feedbackId: FeedbackId.fromString('fb-2024-feature-001'),
previousStatus: FeedbackStatus('new'),
newStatus: FeedbackStatus('in_review'),
changedBy: UserId.fromString('product-manager'),
reason: 'Prioritized for Q2 development',
response: null,
);
// Support team acknowledges and provides initial status
final firstResponsePayload = AddFeedbackResponsePayload(
feedbackId: FeedbackId.fromString('fb-2024-bug-001'),
responseId: FeedbackResponseId.fromString('resp-001'),
responseText: FeedbackResponseText(
'We have received your report about sidebar scrolling. '
'Our team is investigating this issue. We will update you within 48 hours.'
),
respondedBy: UserId.fromString('support-l1'),
isPublic: true,
attachmentIds: [],
);
// Later, provide solution
final solutionPayload = AddFeedbackResponsePayload(
feedbackId: FeedbackId.fromString('fb-2024-bug-001'),
responseId: FeedbackResponseId.fromString('resp-002'),
responseText: FeedbackResponseText(
'We have identified the root cause: a CSS issue with the sidebar overflow. '
'A fix has been applied and is available in version 2.2.0, released today. '
'Please download the latest version and confirm the sidebar scrolls correctly.'
),
respondedBy: UserId.fromString('engineering-team'),
isPublic: true,
attachmentIds: [releaseNotesId],
);
// Update status to resolved after confirmation
final resolvePayload = UpdateFeedbackStatusPayload(
feedbackId: FeedbackId.fromString('fb-2024-bug-001'),
previousStatus: FeedbackStatus('in_progress'),
newStatus: FeedbackStatus('resolved'),
changedBy: UserId.fromString('engineering-team'),
reason: 'Fix verified in production',
response: null,
);
// After applying events, query aggregate state
final feedback = /* hydrated from event store */;
// Check lifecycle state
if (feedback.isInProgress && feedback.hasPublicResponses) {
print('User has been informed of progress');
}
// Analyze aging
if (feedback.isStale) {
print('Feedback is ${feedback.ageInDays} days old and still open - consider closing');
}
// Check urgency
if (feedback.requiresUrgentAttention) {
print('URGENT: ${feedback.title.value}');
}
// Review responses
print('Total responses: ${feedback.responseCount}');
for (final response in feedback.publicResponses) {
print(' [${response.respondedAt}] ${response.respondedBy.value}');
}
// Get latest activity
final latest = feedback.latestResponse;
if (latest != null) {
print('Last activity: ${feedback.daysSinceLastActivity} days ago');
}
User submits bug report (FeedbackSubmitted)
Support team triages (StatusChanged: new → in_review)
Engineering confirms issue (Response added: public)
Work begins on fix (StatusChanged: in_review → in_progress)
Fix deployed (Response added: public with release notes)
User confirms resolution (Response added: public acknowledgement)
Marked resolved (StatusChanged: in_progress → resolved)
Closed after 30 days inactivity (StatusChanged: resolved → closed)
User submits feature request (FeedbackSubmitted)
Product team reviews (StatusChanged: new → in_review)
Internal discussion (Response added: private/internal notes)
Decision: Either approved or declined
If approved:
- Update status (StatusChanged: in_review → in_progress)
- Provide user feedback (Response added: public)
If declined:
- Update status (StatusChanged: new → rejected)
- Explain rationale (Response added: public or internal)

Cross-domain communication uses value objects from contracts:

  • FeedbackId, FeedbackTitle, FeedbackDescription: Core value objects
  • FeedbackType, FeedbackPriority, FeedbackCategory, FeedbackStatus: Enumerations
  • UserId, EmailAddress: User references
  • AttachmentId: References to attachments from attachments domain
  • BrowserInfo, DeviceInfo: Context information

Feedback supports attachments at two levels:

  1. Feedback-level attachments: Files submitted with the initial feedback (e.g., screenshots, logs)
  2. Response-level attachments: Files attached to responses (e.g., patches, release notes)

User tracking through:

  • submittedBy: User who submitted feedback
  • respondedBy: Staff member providing response
  • changedBy: User making status updates

When using the feedback domain, register types for serialization:

import 'package:feedback_v1/feedback_v1.dart';
// Register all feedback domain types
FeedbackV1().registerDomainTypes();
// Or explicitly:
registerFeedbackV1();

This registers:

  • Aggregates: FeedbackAggregate
  • Events: FeedbackSubmittedEvent, FeedbackStatusChangedEvent, FeedbackResponseAddedEvent
  • Directives: SubmitFeedbackDirective, UpdateFeedbackStatusDirective, AddFeedbackResponseDirective
  • Payloads: SubmitFeedbackPayload, UpdateFeedbackStatusPayload, AddFeedbackResponsePayload

The FeedbackAggregate.validate() method enforces business rules:

void validate() {
// Empty aggregates are valid (used during snapshot creation)
if (feedbackId == FeedbackId.unresolved &&
title == FeedbackTitle.unresolved) {
return;
}
// ID must be resolved
if (feedbackId == FeedbackId.unresolved) {
throw StateError('Feedback ID cannot be unresolved');
}
// Title must not be empty
if (title.value.trim().isEmpty) {
throw StateError('Feedback title cannot be empty');
}
// Description must not be empty
if (description.value.trim().isEmpty) {
throw StateError('Feedback description cannot be empty');
}
// Status must be valid
const validStatuses = [
'new',
'in_review',
'in_progress',
'resolved',
'closed',
'rejected'
];
if (!validStatuses.contains(status.value)) {
throw StateError('Invalid feedback status: ${status.value}');
}
// Type must be valid
const validTypes = [
'bug_report',
'feature_request',
'general_feedback',
'issue'
];
if (!validTypes.contains(feedbackType.value)) {
throw StateError('Invalid feedback type: ${feedbackType.value}');
}
// Priority must be valid
const validPriorities = ['low', 'medium', 'high', 'urgent'];
if (!validPriorities.contains(priority.value)) {
throw StateError('Invalid priority: ${priority.value}');
}
// Category must be valid
const validCategories = [
'ui_ux',
'performance',
'functionality',
'data',
'other'
];
if (!validCategories.contains(category.value)) {
throw StateError('Invalid category: ${category.value}');
}
// Resolved feedback must have at least one response
if (status.value == 'resolved' && _responses.isEmpty) {
throw StateError('Resolved feedback must have at least one response');
}
}
ErrorCauseResolution
Title cannot be emptyFeedback submitted without titleRequire non-empty title in UI
Invalid feedback typeType not in enumerated listValidate type against allowed values
Resolved feedback must have responsesAttempting to mark resolved without responseAdd response before resolving
Invalid status transitionStatus change violates workflowCheck current status before transitioning

The domain includes tests for lifecycle and serialization:

import 'package:test/test.dart';
import 'package:feedback_v1/feedback_v1.dart';
group('FeedbackAggregate', () {
test('lifecycle and status transitions', () {
final id = AggregateId('fb-1');
final now = DateTime(2024);
final feedback = FeedbackAggregate(
feedbackId: FeedbackId('FB-1'),
title: FeedbackTitle('Test Feedback'),
description: FeedbackDescription('Test Description'),
feedbackType: FeedbackType('bug_report'),
priority: FeedbackPriority('medium'),
category: FeedbackCategory('functionality'),
submittedBy: UserId('USR-1'),
submittedAt: now,
updatedAt: now,
status: FeedbackStatus('new'),
)..setIdForFramework(id);
expect(feedback.isNew, isTrue);
expect(feedback.isOpen, isTrue);
// Submit for review
final inReview = feedback.apply(FeedbackStatusChangedEvent(
feedbackId: FeedbackId('FB-1'),
previousStatus: FeedbackStatus('new'),
newStatus: FeedbackStatus('in_review'),
changedBy: UserId('USR-2'),
changedAt: now.add(Duration(hours: 1)),
));
expect(inReview.isInReview, isTrue);
expect(inReview.isOpen, isTrue);
// Add response
final withResponse = inReview.apply(FeedbackResponseAddedEvent(
feedbackId: FeedbackId('FB-1'),
responseId: FeedbackResponseId('RESP-1'),
responseText: FeedbackResponseText('We are investigating this issue'),
respondedBy: UserId('SUPPORT-1'),
respondedAt: now.add(Duration(hours: 2)),
isPublic: true,
));
expect(withResponse.hasResponses, isTrue);
expect(withResponse.responseCount, 1);
expect(withResponse.canBeResolved(), isTrue);
// Mark resolved
final resolved = withResponse.apply(FeedbackStatusChangedEvent(
feedbackId: FeedbackId('FB-1'),
previousStatus: FeedbackStatus('in_review'),
newStatus: FeedbackStatus('resolved'),
changedBy: UserId('SUPPORT-1'),
changedAt: now.add(Duration(hours: 24)),
response: 'Fix deployed in version 2.0.1',
));
expect(resolved.isResolved, isTrue);
expect(resolved.isOpen, isFalse);
});
test('serialization and deserialization', () {
final json = {
'feedbackId': 'FB-1',
'title': 'Test Feedback',
'description': 'Test Description',
'feedbackType': 'bug_report',
'priority': 'medium',
'category': 'functionality',
'submittedBy': 'USR-1',
'submittedAt': '2024-01-01T00:00:00Z',
'updatedAt': '2024-01-01T00:00:00Z',
'status': 'new',
'responses': [],
'attachmentIds': [],
'metadata': {},
};
final feedback = FeedbackAggregate.fromJson(
json,
AggregateId('fb-1'),
);
expect(feedback.feedbackId.value, 'FB-1');
expect(feedback.title.value, 'Test Feedback');
expect(feedback.isNew, isTrue);
expect(feedback.responseCount, 0);
});
});

The feedback domain depends on:

  • nomos_core: Core event sourcing framework

    • Aggregate, Event, Directive, Payload base classes
    • Registry mechanisms for type serialization
    • AggregateId, AggregateBase infrastructure
  • contracts_v1: Cross-domain value objects

    • FeedbackId, FeedbackTitle, FeedbackDescription, FeedbackType, FeedbackStatus
    • FeedbackPriority, FeedbackCategory
    • UserId, EmailAddress, AttachmentId
    • BrowserInfo, DeviceInfo

No domain-specific dependencies—designed for loose coupling and cross-domain communication.

// Good: Check preconditions
if (feedback.isOpen) {
// Execute status update
}
// Avoid: Assume state
// Execute status update directly

2. Provide User-Visible Responses When Resolving

Section titled “2. Provide User-Visible Responses When Resolving”
// Good: User gets meaningful update
final response = AddFeedbackResponsePayload(
feedbackId: feedbackId,
responseId: responseId,
responseText: FeedbackResponseText(
'Your issue has been fixed in version 2.1.0. Please update and let us know '
'if the problem persists.'
),
respondedBy: engineerUserId,
isPublic: true,
attachmentIds: [releaseNotesId],
);
// Avoid: Resolving without user communication
// Status changed to resolved without response
// Good: Rich context for reproduction
final bugReport = SubmitFeedbackPayload(
// ... required fields ...
browserInfo: BrowserInfo('Chrome 121.0 on Windows 11'),
deviceInfo: DeviceInfo('Desktop, 1920x1080'),
attachmentIds: [screenshotId, errorLogId],
metadata: {
'timestamp': DateTime.now().toIso8601String(),
'appVersion': '2.1.0',
'userAgent': userAgent,
},
);
// Avoid: Minimal context
// Only title and description, no environment details
// Good: Priority matches impact
if (affectsLogin) {
priority = FeedbackPriority('urgent');
} else if (affectsMultipleUsers) {
priority = FeedbackPriority('high');
} else if (workaround) {
priority = FeedbackPriority('medium');
}
// Avoid: All feedback marked high/urgent
// Dilutes actual priorities
// Good: Close resolved feedback after period
if (feedback.isResolved && feedback.ageInDays > 30) {
// Mark as closed
}
// Monitor stale open feedback
if (feedback.isStale) {
// Escalate or resolve
}
// Avoid: Accumulating open feedback indefinitely
  • Contracts Domain: Value object definitions and cross-domain types
  • Attachments Domain: File attachment management and storage
  • Identity Domain: User authentication and authorization
  • Intents Domain: High-level user intentions that may trigger feedback workflows