You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Theming, GPS time, mesh, setup, Launcher, and more - #728
Backend id validation:mesh_backend_register() rejects ids that are empty or contain characters other than letters, digits, _ and - (ERROR_INVALID_ARGUMENT). Blocks ../escape storage paths.
Storage events:configure() emits MESH_EVENT_CONTACT and MESH_EVENT_CHANNEL when the selected backend's storage changes. Consumers reload without a radio.
State event on backend switch: a backend becoming active while the state is already ON now emits MESH_EVENT_STATE. Fixes the Mesh settings app not showing a Meshtastic to MeshCore switch, and stale channel lists.
Store opened before publish:onStart opens the selected backend's store before publishing service. The store is closed on thread-start failure.
Query errors:mesh_history_get, mesh_conversation_get and mesh_get_contacts return ERROR_INVALID_STATE when a backend is selected but its store is closed.
Contacts load state:loadContacts() reports failure and the service tracks contactsLoaded. Contact add, remove and save are blocked until a load succeeds, so a failed read can't overwrite saved contacts. mesh_get_contacts returns an error meanwhile. A failed load is retried on the next configure().
Feature check: new enum MeshFeature (MESH_FEATURE_NAMED_CHANNELS, MESH_FEATURE_KEYED_CHANNELS) in mesh.h, uint32_t features in MeshBackendApi, and bool mesh_supports(uint32_t features) (exported). MeshCore declares both, Meshtastic only keyed.
Channels app: the "Add channel" dialog lists only supported options, and the "+" button is hidden when none are supported.
Tests: backend id rejection, storage and state events, contacts load failure (contacts path is a directory), mesh_supports.
Finding, no change: "Join public channel" can't work on Meshtastic as implemented. It sends an empty key, which meshtastic_expand_psk() rejects. The protocol's public channels use the default PSK with a name. Not implemented.
Location and GPS time
Subscriber descriptions:location_subscribe(sub, event_group, description) takes a description, truncated to 24 bytes at a UTF-8 boundary. New location_for_each_subscriber(). The Location settings app shows a "Subscribers" card (focusable list) at the bottom, refreshed every second.
Time event restrictions in location_host_report():
only from the selected provider (isBest)
only from providers with the new provides_time flag (GPS)
only for plausible dates, 2025-01-01 to 2100-01-01
GPS time service:
subscribes to location as soon as location services are enabled, via a 2 s timer
unsubscribes after the time was announced
Setup "Time from GPS" step: defaults to Disabled unless location was enabled in the previous step. Steps can supply a defaultChoice.
Time & Date app: the GPS row's explanation text refreshes when location settings change, on the app's task.
Tests: subscriber descriptions and UTF-8 truncation, time events once per minute, implausible dates ignored.
Theme (spacing and size)
lvgl_theme_lerp() computes in 64-bit, with a test for extreme values.
Button padding follows $size instead of $spacing:
@density-control-pad-hor/ver in both themes
@text-button-pad-hor in material.lss
themes regenerated
Minimum paddings: vertical 1, horizontal 2.
AppearanceSettings loader: an explicit empty spacing= or size= overrides the legacy density key (loads as unset). Test updated.
Device properties lvgl.uiDensity migrated to lvgl.uiSpacing and lvgl.uiSize. Tactility/Kconfig has TT_UI_SPACING and TT_UI_SIZE. device.py writes them, with validation. theme.cpp reads them. The docs are updated in ../TactilityDocs, uncommitted.
Setup app
Layout:
content scrolls above the button bar, so Back and Skip no longer cover it
the content takes keyboard focus only when it overflows
hidden choice chips are disabled so the focus group skips them
Navigation: the spacer in the button row is not clickable, so grid navigation skips it.
Last page: "You're all set" focuses Finish when there is no touch screen.
Title font: small at size 20 or below, regular below 50, large otherwise.
Statusbar: hidden while Setup is shown, via new systemBarsSetStatusbarVisible().
A moveButtonsLast() change was made and then reverted at your request.
Boot
SD card missing: with power-off support, shows [ Reboot ] / Power off options.
keyboard and encoder: arrows, Tab or wheel select, Enter confirms
touch: tap selects, long press confirms
Without power-off support, the old "Press any key to reboot" prompt remains.
Logo selection: picks the largest logo that is at most 60% of the screen width and height and at most an eighth of its area. The smallest is used if none fits. Size is read from the PNG header.
Diagnostic:Power off supported: 0/1 log line. Your cl32-v04 was running old firmware, so there was no bug.
Launcher and TileGrid
Side mode: the side container is applied before the grid is populated, so the grid is laid out at its final size.
Bar buttons: the grid is populated after the bar buttons are added, through the new finishWidgetsWithBottomBar(). Fixes a one-frame jump on lilygo-thmi (portrait, split bars).
The jump you reported may still be there. I didn't confirm the split-bars cause.
Kernel
path_tree_size() (used by du) is iterative: no recursion, one open directory at a time, work list on the heap. It also fixes the silent skipping of subdirectories beyond 32 per directory and of names of 64 bytes or more. New test path_tree_size_test.cpp.
directory_remove_tree() is unchanged and still recursive.
It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.
No actionable comments were generated in the recent review.
Recent review info
Run configuration
Source mode: Auto
Repository: Unavailable
Branch: Unavailable
File: No configuration file from this repository applied
Commit: Unavailable
Layers: Organization UI
Review profile: CHILL
Plan: Advanced
Run ID: 8ad5f619-a12d-4865-9884-c37d68813827
Commits
Reviewing files that changed from the base of the PR and between 1c741e4 and 78bdd6e.
Files selected for processing: 3
Documentation/ideas.md
Tactility/Private/Tactility/app/boot/BootScreen.h
Tactility/Source/app/boot/BootInit.cpp
Files skipped from review as they are similar to previous changes: 1
Documentation/ideas.md
Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.
Walkthrough
The pull request adds independent spacing and size controls to LVGL themes and appearance settings. It adds network availability checks, location subscriber descriptions, rate-limited location-time events, and optional GPS time synchronization. Mesh storage becomes backend-specific, with migration from shared storage. Boot screens gain reboot and power-off choices, and setup gains versioned completion and configurable choice pages. The changes also update ESP32 SDK release mappings.
Merge Risk: Moderate · up to 78bdd
Resolve the remaining mesh migration and recovery issues before merging, alongside incomplete SDK component registration and the boot power-off fallback. Smaller setup and appearance inconsistencies also remain. The appearance precedence and failed-contact-load protections are now corrected.
Pre-merge checks: 4 · 1
Failed checks: 1 warning
Check name
Status
Explanation
Resolution
Docstring Coverage
Docstring coverage is 27.55% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta…
Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed checks: 4 passed
Check name
Status
Explanation
Description Check
Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check
Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check
Check skipped because no linked issues were found for this pull request.
Title check
The title names several major changes in the pull request, including theming, GPS time, mesh, and setup. It is broad and includes the vague phrase "and more," but it still conveys meaningful change ar…
Full details: Title check
Explanation
The title names several major changes in the pull request, including theming, GPS time, mesh, and setup. It is broad and includes the vague phrase "and more," but it still conveys meaningful change areas.
Full details: Docstring Coverage
Explanation
Docstring coverage is 27.55% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 98 functions across 54 files. (1 skipped: 1 unsupported.)
Resolution
Write docstrings for the functions missing them to satisfy the coverage threshold.
Finishing Touches
Generate docstrings
Commit to this branch
Create a new PR
Generate unit tests (beta)
Commit to this branch
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 20
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
Major comments: 7
Tactility/Source/app/boot/BootInit.cpp: 1
218-222: Major · Stability & Availability · Quick win
Stop the boot screen from waiting forever after a successful power-off request.
After powerOff() returns true, the loop at BootInit.cpp:220-222 never exits. The papers3 driver returns ERROR_NONE even when the device stays on. The device then stays on the boot screen with no retry or reboot. Replace the infinite wait with a bounded delay so the retry prompt returns.
Suggested fix
while (waitForBootChoice(screen, {}, message) == BootChoice::PowerOff) {
if (powerOff()) {
- // Waits for the power to go away- while (true) {- delay_millis(1000);- }+ // Give the power supply time to cut power.+ delay_millis(5000);
}
LOG_E(TAG, "Power off failed");
message = lines;
message.push_back("Power off failed.");
}
Modules/mesh-module/source/mesh_service.cpp: 6
436-436: Major · Data Integrity & Integration · Quick win
Keep added contacts when no backend storage is open.
With no backend open, mesh_add_contact returns success, but the contact is dropped at the next storage switch or service stop. Reject the add with ERROR_INVALID_STATE when storageBackend is empty. The base code saved these contacts to the shared contacts file.
Suggested fix
lock();
error_t result = ERROR_INVALID_STATE;
- if (service != nullptr) {+ if (service != nullptr && service->storageBackend[0] != '\0') {
result = addContact(service, *address, name, true);
}
unlock();
1123-1125: Major · Data Integrity & Integration · Quick win
Check the target flush result before closing the migration store.
migrateSharedMessages() calls close() on each target store without checking whether the final flush succeeded. MeshStore::close() discards the flush result and frees the buffer, so the pending records are lost. The function still returns success, and migrateSharedStorage() then removes the shared store.
Suggested fix
for (size_t t = 0; t < targetCount; t++) {
- targets[t].store->close();+ if (targets[t].store->flush() != ERROR_NONE) {+ ok = false;+ }+ targets[t].store->close();
tt::deleteObject(targets[t].store);
}
1092-1092: Major · Data Integrity & Integration · Heavy lift
Do not delete the shared store when getAfter() skipped a message.
getAfter() drops entries whose readEntry() fails and returns only the successful reads. migrateSharedMessages() does not set ok to false for these skips, so migrateSharedStorage() removes the shared store. Compare the total read count with shared.getMessageCount(), and keep the shared store when they differ. A retry must also skip messages that already reached a backend store.
1148-1152: Major · Data Integrity & Integration · Quick win
Check fclose in migrateSharedContacts and do not remove the shared file after a failed flush.
migrateSharedContacts checks the fwrite result but ignores the fclose result. A flush error at fclose leaves ok true. migrateSharedStorage then deletes the shared contacts.bin, so that contact is lost. The fix is to treat a non-zero fclose return as a failure and keep the shared file for a retry.
Suggested fix
char path[FILE_MAX_PATH_STRING_LENGTH];
FILE* target = getBackendPath(contact.backend, CONTACTS_FILE_NAME, path, sizeof(path), true) ? fopen(path, "ab") : nullptr;
- if (target == nullptr || fwrite(&contact, sizeof(contact), 1, target) != 1) {+ if (target == nullptr) {
ok = false;
- }- if (target != nullptr) {- fclose(target);+ } else {+ if (fwrite(&contact, sizeof(contact), 1, target) != 1) {+ ok = false;+ }+ if (fclose(target) != 0) {+ ok = false;+ }
}
1097-1099: Major · Data Integrity & Integration · Heavy lift
Preserve legacy records that use dotted backend IDs.
migrateSharedMessages() skips records with invalid backend IDs, but migrateSharedStorage() still removes the shared store when migration reports success. Since mesh_backend_register() accepts dotted IDs, rejecting future registrations does not preserve records written by older versions.
Keep skipped records in the shared store or quarantine them. Do not only set ok = false: MeshStore::add() appends records, so retries could duplicate records already copied.
1137-1152: Major · Data Integrity & Integration · Quick win
Do not delete the shared contacts file after a failed or partial read.
migrateSharedContacts exits its loop whenever fread returns less than one complete MeshContact. It does not check ferror(file) or detect a truncated final record, so ok remains true. migrateSharedStorage then calls file_remove(contactsPath), which can permanently remove contacts that were not migrated.
Suggested fix
- while (fread(&contact, sizeof(contact), 1, file) == 1) {+ while (true) {+ const size_t bytesRead = fread(&contact, 1, sizeof(contact), file);+ if (bytesRead == 0) {+ if (ferror(file)) {+ ok = false;+ }+ break;+ }+ if (bytesRead != sizeof(contact)) {+ ok = false;+ break;+ }
contact.backend[MESH_BACKEND_ID_MAX_LENGTH] = '\0';
Minor comments: 7
Tactility/Source/app/setup/Setup.cpp: 3
583-583: Minor · Stability & Availability · Poor tradeoff
Move the GPS settings write off the LVGL thread.
The Continue callback runs the selected step's run() lambda synchronously on the LVGL thread. The new GPS step's lambda calls settings::setGpsTimeSyncAtBoot(), which opens a preferences file and writes a value. A slow storage device can block the UI until that write returns. Store the GPS choice in the setup context and perform the write from the setup task after it receives an event. Keep the step's advanceTo() call on the LVGL side.
As per coding guidelines: "Code that runs on the LVGL thread must not do file operations (including reading or writing settings): create_widgets, LVGL event callbacks, timers, and anything under the LVGL lock."
124-124: Minor · Performance & Scalability · Quick win
Use tt::std containers for the new choice labels.
The new StepConfiguration::choices is a std::vector<std::string>. Setup keeps these steps for the whole run, so the vector and its strings use the default allocator. The base code stored these labels in a flash-resident LOCATION_CHOICES array. Use tt::std::vector<tt::std::string> and include its header.
As per coding guidelines: "Containers that live long use the tt::std replacements from TactilityKernel: tt::std::vector, tt::std::deque, tt::std::map and tt::std::string... They prefer external RAM."
583-584: Minor · Data Integrity & Integration · Quick win
Report failed GPS preference writes in the setup step.
setGpsTimeSyncAtBoot() silently returns when preferences cannot be opened. The setup step then advances and can mark setup complete. Because preferences_put_bool() returns void, the current code cannot detect its write result. Expose a status from the complete persistence path and advance only after a successful write.
Modules/mesh-module/source/mesh_service.cpp: 2
1117-1119: Minor · Data Integrity & Integration · Quick win
Make shared-message migration retry-safe.
MeshStore::add always assigns a new sequence and appends a record. If a later backend fails, earlier copies remain while the shared store is retained. The next startup adds them again, and mesh_history_get returns both records. Roll back copied records or persist migration progress.
1144-1146: Minor · Data Integrity & Integration · Quick win
Skip migrated contacts that already exist in the destination.
migrateSharedContacts appends each contact to the backend file and does not roll back earlier writes when a later write fails. The shared contacts file remains in place after that failure, so the next startup repeats the migration. loadContacts and mesh_get_contacts do not deduplicate contacts by address.
A temporary file containing only migrated records is not sufficient. Renaming it over an existing destination file can discard contacts already stored there. Use address-based deduplication, or an atomic progress strategy that preserves existing destination records.
41-43: Minor · Stability & Availability · Quick win
Keep GPS synchronization active after a failed clock update.
When setSystemTime(timeMs) fails, done remains true because done.exchange(true) runs first. The timer then unsubscribes from location updates, so later fixes cannot retry during the same boot. Keep in-flight exclusion separate from terminal completion, and set done after a successful update or an accepted no-op.
84-85: Minor · Stability & Availability · Quick win
Make gpsSyncAtBoot atomic.
onGpsSyncChanged writes the plain Context::gpsSyncAtBoot on the LVGL task. appMain reads it on the app task after the event signal. A second toggle can write the field while the app task handles the earlier signal. The event group does not protect the context field.
Follow-up findings are listed below so you can review them separately from inline comments.
Follow-ups (2)
Minor (2)
Minor · Seek to afterSeq in getAfter instead of scanning from the first entry. · mesh_store.cpp:455
Modules/mesh-module/source/mesh_store.cpp:455 Minor · Performance & Scalability · Quick win
Seek to afterSeq in getAfter instead of scanning from the first entry.
getAfter restarts at index.begin() on every call. The migration loop calls it once per batch of eight messages, so the in-RAM index walk grows roughly with N²/16 for N messages. Storage reads stay linear. The index is sorted by seq, so std::upper_bound can start at the first newer entry.
Minor · Keep buffered messages when the flush in MeshStore::close() fails. · mesh_service.cpp:461
Modules/mesh-module/source/mesh_service.cpp:461 Minor · Data Integrity & Integration · Poor tradeoff
Keep buffered messages when the flush in MeshStore::close() fails.
MeshStore::close() ignores the result of flush(). It then frees the buffer and clears the index, so unflushed messages are lost. switchStorage() ignores the result of close() too, so the backend switch continues. Make close() report the flush error. Abort the switch and keep the store open when the flush fails.
theme_test.cpp tests only lvgl_theme_lerp. It does not verify that spacing or size changes a rendered theme style. Add a focused LVGL theme test that sets a concrete density value, creates a representative themed widget, and checks a style such as container padding or button padding.
The ../escape fixture is skipped by migrateSharedMessages, but the test does not check the corresponding resolved path. If validation regresses, migration can create <mesh data>/escape while the current assertions still pass.
Suggested test assertion
// The shared files are gone, and each backend has its own
CHECK_FALSE(directory_exists(storePath));
CHECK_EQ(access(contactsPath, F_OK), -1);
++ char escapePath[FILE_MAX_PATH_STRING_LENGTH];+ REQUIRE_EQ(service_paths_get_user_data_path(MESH_SERVICE_ID, "escape", escapePath, sizeof(escapePath)), ERROR_NONE);+ CHECK_FALSE(directory_exists(escapePath));
Add a disconnected-first, connected-second regression case.
The test registers only one Wi-Fi device. Add a disconnected device before a connected device to verify that network_is_available() scans all Wi-Fi devices. This protects against a regression that could make location lookups return ERROR_RESOURCE when a later device is connected.
Tactility/Tests/Source/GpsTimeSyncTest.cpp: 1
24-28: Trivial · Maintainability & Code Quality · Heavy lift
Add integration coverage for GPS time service startup and delivery.
GpsTimeSyncTest.cpp tests shouldApplyGpsTime() and calls GpsTimeSyncService::onLocationTime(...) directly. It does not exercise onStart(), callback registration, or location_subscribe(). A broken startup path can therefore remain undetected while the tests pass. Add a test that starts the service and delivers a KERNEL_EVENT_LOCATION_TIME event.
Review info
Run configuration
Source mode: Auto
Repository: Unavailable
Branch: Unavailable
File: No configuration file from this repository applied
Commit: Unavailable
Layers: Organization UI
Review profile: CHILL
Plan: Advanced
Run ID: 6511b780-04f6-4697-8f72-7258c0704e89
Commits
Reviewing files that changed from the base of the PR and between 752923b and d88aee1.
Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.
Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.
Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.
The reason will be displayed to describe this comment to others. Learn more.
Follow-up Comments
Failed contacts loads are not retried from tick().
464-465: Minor · Stability & Availability · Poor tradeoff
Failed contacts loads are not retried from tick().
When loadContacts() fails, contactsLoaded stays false. tick() never calls loadContacts() or switchStorage(), so the load runs again only when configure() runs. That happens on radio attach, region change, backend changes, or service restart. Until one of these events occurs, contact operations return ERROR_INVALID_STATE, even after the contacts file becomes readable.
File: No configuration file from this repository applied
Commit: Unavailable
Layers: Organization UI
Review profile: CHILL
Plan: Advanced
Run ID: 278937ca-2d21-48b9-91d3-e67a66566ca5
Commits
Reviewing files that changed from the base of the PR and between 6e6f769 and 1c741e4.
Files selected for processing: 2
Modules/mesh-module/source/mesh_service.cpp
Modules/mesh-module/tests/source/service_test.cpp
Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.
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
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.
Mesh
mesh_backend_register()rejects ids that are empty or contain characters other than letters, digits,_and-(ERROR_INVALID_ARGUMENT). Blocks../escapestorage paths.configure()emitsMESH_EVENT_CONTACTandMESH_EVENT_CHANNELwhen the selected backend's storage changes. Consumers reload without a radio.MESH_EVENT_STATE. Fixes the Mesh settings app not showing a Meshtastic to MeshCore switch, and stale channel lists.onStartopens the selected backend's store before publishingservice. The store is closed on thread-start failure.mesh_history_get,mesh_conversation_getandmesh_get_contactsreturnERROR_INVALID_STATEwhen a backend is selected but its store is closed.loadContacts()reports failure and the service trackscontactsLoaded. Contact add, remove and save are blocked until a load succeeds, so a failed read can't overwrite saved contacts.mesh_get_contactsreturns an error meanwhile. A failed load is retried on the nextconfigure().enum MeshFeature(MESH_FEATURE_NAMED_CHANNELS,MESH_FEATURE_KEYED_CHANNELS) inmesh.h,uint32_t featuresinMeshBackendApi, andbool mesh_supports(uint32_t features)(exported). MeshCore declares both, Meshtastic only keyed.mesh_supports.meshtastic_expand_psk()rejects. The protocol's public channels use the default PSK with a name. Not implemented.Location and GPS time
location_subscribe(sub, event_group, description)takes a description, truncated to 24 bytes at a UTF-8 boundary. Newlocation_for_each_subscriber(). The Location settings app shows a "Subscribers" card (focusable list) at the bottom, refreshed every second.location_host_report():isBest)provides_timeflag (GPS)defaultChoice.Theme (spacing and size)
lvgl_theme_lerp()computes in 64-bit, with a test for extreme values.$sizeinstead of$spacing:@density-control-pad-hor/verin both themes@text-button-pad-horinmaterial.lssAppearanceSettingsloader: an explicit emptyspacing=orsize=overrides the legacydensitykey (loads as unset). Test updated.lvgl.uiDensitymigrated tolvgl.uiSpacingandlvgl.uiSize.Tactility/KconfighasTT_UI_SPACINGandTT_UI_SIZE.device.pywrites them, with validation.theme.cppreads them. The docs are updated in../TactilityDocs, uncommitted.Setup app
systemBarsSetStatusbarVisible().moveButtonsLast()change was made and then reverted at your request.Boot
[ Reboot ]/Power offoptions.Power off supported: 0/1log line. Your cl32-v04 was running old firmware, so there was no bug.Launcher and TileGrid
finishWidgetsWithBottomBar(). Fixes a one-frame jump on lilygo-thmi (portrait, split bars).Kernel
path_tree_size()(used bydu) is iterative: no recursion, one open directory at a time, work list on the heap. It also fixes the silent skipping of subdirectories beyond 32 per directory and of names of 64 bytes or more. New testpath_tree_size_test.cpp.directory_remove_tree()is unchanged and still recursive.