Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 66 additions & 4 deletions Backend/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pub struct AppConfig {
#[serde(default)]
pub git: Git,
#[serde(default)]
pub commit: Commit,
#[serde(default)]
pub credentials: Credentials,
#[serde(default)]
pub diff: Diff,
Expand Down Expand Up @@ -58,6 +60,7 @@ impl Default for AppConfig {
plugin: Default::default(),
general: Default::default(),
git: Default::default(),
commit: Default::default(),
credentials: Default::default(),
diff: Default::default(),
lfs: Default::default(),
Expand Down Expand Up @@ -93,9 +96,6 @@ pub struct General {
pub telemetry: bool,
#[serde(default = "default_true")]
pub crash_reports: bool,
/// When enabled, commit hooks may not rewrite the user-entered commit summary.
#[serde(default = "default_true")]
pub restrict_commit_summary: bool,
}
impl Default for General {
/// Returns default general settings values.
Expand All @@ -113,7 +113,6 @@ impl Default for General {
checks_on_launch: true,
telemetry: false,
crash_reports: true,
restrict_commit_summary: true,
}
}
}
Expand Down Expand Up @@ -179,6 +178,69 @@ impl Default for Git {
}
}

/// Commit settings for commit-message prefill behavior.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Commit {
/// Enables commit summary prefill when one full file is selected.
#[serde(default = "default_true")]
pub commit_message_template_enabled: bool,
/// When enabled, commit hooks may not rewrite the user-entered commit summary.
#[serde(default = "default_true")]
pub restrict_commit_summary: bool,
/// Commit summary templates used for single-file prefill.
#[serde(default)]
pub commit_templates: CommitTemplates,
}
impl Default for Commit {
/// Returns default commit template settings values.
fn default() -> Self {
Self {
commit_message_template_enabled: true,
restrict_commit_summary: true,
commit_templates: Default::default(),
}
}
}

/// Commit template strings used for single-file prefill.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CommitTemplates {
/// Commit summary template used for single new-file prefill.
#[serde(default = "default_commit_message_create_template")]
pub commit_message_template_create: String,
/// Commit summary template used for single modified-file prefill.
#[serde(default = "default_commit_message_update_template")]
pub commit_message_template_update: String,
/// Commit summary template used for single deleted-file prefill.
#[serde(default = "default_commit_message_delete_template")]
pub commit_message_template_delete: String,
}
impl Default for CommitTemplates {
/// Returns default commit template strings.
fn default() -> Self {
Self {
commit_message_template_create: default_commit_message_create_template(),
commit_message_template_update: default_commit_message_update_template(),
commit_message_template_delete: default_commit_message_delete_template(),
}
}
}

/// Returns the default template used for creating a commit from one new file.
fn default_commit_message_create_template() -> String {
"Create {file:name}".to_string()
}

/// Returns the default template used for updating a commit from one modified file.
fn default_commit_message_update_template() -> String {
"Update {file:name}".to_string()
}

/// Returns the default template used for deleting a commit from one deleted file.
fn default_commit_message_delete_template() -> String {
"Delete {file:name}".to_string()
}

/// Settings for authentication and signing tools.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Credentials {
Expand Down
49 changes: 41 additions & 8 deletions Frontend/src/modals/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ <h3 id="settings-title" style="margin:0">Settings</h3>
<ul id="settings-nav" class="list">
<li><button class="seg-btn active" type="button" data-section="general">General</button></li>
<li><button class="seg-btn" type="button" data-section="diff">Diff &amp; Merge</button></li>
<li><button class="seg-btn" type="button" data-section="commit">Commit</button></li>
<li><button class="seg-btn" type="button" data-section="performance">Performance</button></li>
<li><button class="seg-btn" type="button" data-section="ux">UX</button></li>
<li><button class="seg-btn" type="button" data-section="logging">Logging</button></li>
Expand Down Expand Up @@ -97,14 +98,6 @@ <h3 id="settings-title" style="margin:0">Settings</h3>
</label>
</div>

<div class="group">
<label class="checkbox">
<input type="checkbox" id="set-restrict-commit-summary" />
Limit commit summary to 72 characters
<span class="help-tip" title="When enabled, the commit summary box caps input at 72 characters so it fits a standard Git commit title. Enabled by default.">?</span>
</label>
</div>

</form>

<!-- Diff -->
Expand Down Expand Up @@ -171,6 +164,46 @@ <h3 id="settings-title" style="margin:0">Settings</h3>
</div>
</form>

<!-- Commit -->
<form class="panel-form hidden" data-panel="commit">
<div class="group">
<label class="checkbox">
<input type="checkbox" id="set-commit-message-template-enabled" />
Prefill commit summary from file status
<span class="help-tip" title="When enabled, a single full-file selection can fill the commit summary from the templates below. Enabled by default.">?</span>
</label>
</div>

<div class="group">
<label class="checkbox">
<input type="checkbox" id="set-restrict-commit-summary" />
Limit commit summary to 72 characters
<span class="help-tip" title="When enabled, the commit summary box caps input at 72 characters so it fits a standard Git commit title. Enabled by default.">?</span>
</label>
</div>

<div class="group">
<label for="set-commit-message-template-create">Create template
<span class="help-tip" title="Used for new and untracked files.">?</span>
</label>
<input id="set-commit-message-template-create" type="text" placeholder="Create {file:name}" />
</div>

<div class="group">
<label for="set-commit-message-template-update">Update template
<span class="help-tip" title="Used for modified, renamed, and copied files.">?</span>
</label>
<input id="set-commit-message-template-update" type="text" placeholder="Update {file:name}" />
</div>

<div class="group">
<label for="set-commit-message-template-delete">Delete template
<span class="help-tip" title="Used for deleted files.">?</span>
</label>
<input id="set-commit-message-template-delete" type="text" placeholder="Delete {file:name}" />
</div>
</form>

<!-- Performance -->
<form class="panel-form hidden" data-panel="performance">
<div class="group">
Expand Down
17 changes: 17 additions & 0 deletions Frontend/src/scripts/features/diff.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ vi.mock('../lib/tauri', () => {
vi.mock('./repo', () => ({
hydrateStatus: vi.fn(async () => {}),
hydrateCommits: vi.fn(async () => {}),
yieldToPaint: vi.fn(async () => {}),
}));

let state: typeof import('../state/state').state;
Expand Down Expand Up @@ -83,4 +84,20 @@ describe('bindCommit', () => {
stagePaths: ['content/posts/2026/05/openvcs-announcement.md'],
});
});

it('yields to paint before committing', async () => {
const repo = await import('./repo');
const { bindCommit } = await import('./diff');
const commitSummary = document.getElementById('commit-summary') as HTMLInputElement;
const commitBtn = document.getElementById('commit-btn') as HTMLButtonElement;

commitSummary.value = 'Add post';
commitBtn.disabled = false;

bindCommit();
commitBtn.click();

await Promise.resolve();
expect(vi.mocked(repo.yieldToPaint)).toHaveBeenCalled();
});
});
5 changes: 4 additions & 1 deletion Frontend/src/scripts/features/diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import { notify } from '../lib/notify';
import { state } from '../state/state';
import { hydrateStatus, hydrateCommits } from './repo';
import { runHook } from '../plugins';
import { getCommitSummaryHint } from './repo/commit';
import { yieldToPaint } from './repo';

export function bindCommit() {
const commitBtn = qs<HTMLButtonElement>('#commit-btn');
const commitSummary = qs<HTMLInputElement>('#commit-summary');
const commitDesc = qs<HTMLTextAreaElement>('#commit-desc');

commitBtn?.addEventListener('click', async () => {
let summary = commitSummary?.value.trim() || '';
let summary = commitSummary?.value.trim() || getCommitSummaryHint() || '';
if (!summary) { commitSummary?.focus(); notify('Summary is required'); return; }
const hunksMap = state.selectedHunksByFile || {};
const linesMap = state.selectedLinesByFile || {};
Expand All @@ -27,6 +29,7 @@ export function bindCommit() {
};
try {
setBusy('Committing…');
await yieldToPaint();
let description = commitDesc?.value || '';

// Build a combined patch when any file has partial hunks/lines selected.
Expand Down
Loading
Loading