You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/09/13 11:24:18 UTC
(maka) 01/01: fix(ui): open magnetic model pickers without shifting nearby controls
This is an automated email from the ASF dual-hosted git repository.
M4n5ter pushed a commit to branch fix/model-wheel-overlay
in repository https://gitbox.apache.org/repos/asf/maka.git
commit 2af253442faf2fc9a879938f41dc90a710274053
Author: M4n5ter <m4...@gmail.com>
AuthorDate: Sun Sep 13 19:21:22 2026 +0800
fix(ui): open magnetic model pickers without shifting nearby controls
Generated-by: Codex
---
apps/desktop/e2e-budget.json | 2 +-
apps/desktop/e2e/workhub-layout.spec.ts | 36 +++++++++++---------
.../renderer/features/workhub/ui/workhub-root.tsx | 4 +--
apps/desktop/src/renderer/styles/workhub.css | 7 ++++
.../stories/settings/settings-pages.stories.tsx | 13 +++++++-
apps/desktop/stories/workhub.stories.tsx | 32 +++++++++++++++++-
.../ui/src/__tests__/model-wheel-picker.test.tsx | 2 ++
packages/ui/src/model-wheel-picker.tsx | 39 ++++++++++++++++------
packages/ui/src/styles.css | 4 +++
packages/ui/stories/model-picker.stories.tsx | 2 +-
scripts/storybook-visual-smoke.mjs | 1 +
11 files changed, 111 insertions(+), 31 deletions(-)
diff --git a/apps/desktop/e2e-budget.json b/apps/desktop/e2e-budget.json
index d56aab798f..a86076bed8 100644
--- a/apps/desktop/e2e-budget.json
+++ b/apps/desktop/e2e-budget.json
@@ -79,7 +79,7 @@
},
"workhub-layout.spec.ts": {
"tests": 2,
- "electron": "WorkHub model configuration and attachment sending cross the Host boundary; native floating preserves macOS Dock visibility and pointer dragging verifies model-wheel scrolling and release snapping; a held-open Host Turn verifies immediate prompt visibility, durable transcript handoff and persistence after renderer reload"
+ "electron": "WorkHub model configuration and attachment sending cross the Host boundary; the compact native window grows to contain the model overlay without moving composer controls on screen, and pointer dragging scrolls the no-drag wheel instead of moving the frameless window; native floating preserves macOS Dock visibility; a held-open Host Turn verifies immediate prompt visibility, durable transcript handoff and persistence after renderer reload"
},
"workhub-reconstruction.spec.ts": {
"tests": 1,
diff --git a/apps/desktop/e2e/workhub-layout.spec.ts b/apps/desktop/e2e/workhub-layout.spec.ts
index 97deb6089f..15158441d6 100644
--- a/apps/desktop/e2e/workhub-layout.spec.ts
+++ b/apps/desktop/e2e/workhub-layout.spec.ts
@@ -20,7 +20,10 @@
import { FAKE_HOLD_OPEN_PROMPT } from '@maka/runtime/test-only/fake-backend';
import { awaitSendReady, COMPOSER_INPUT, expect, test, getWorkHubPage } from './fixtures';
-test('WorkHub uses its coordination model and shared attachment composer', async ({ sessionLocalWindow: { page, app } }, testInfo) => {
+// The compact native window must contain the top-layer wheel while keeping
+// composer controls fixed on screen; native dragging must scroll the no-drag
+// wheel instead of moving the frameless window. A browser has neither boundary.
+test('WorkHub uses its coordination model and shared attachment composer', async ({ sessionLocalWindow: { page, app } }) => {
await page.evaluate(async () => {
const { connections } = await window.maka.connections.getSnapshot();
const connection = connections.find((entry) => entry.slug === 'e2e')!;
@@ -191,6 +194,14 @@ test('WorkHub uses its coordination model and shared attachment composer', async
await expect.poll(() => workhub.evaluate(() => innerHeight === Math.ceil(document.querySelector('.workHubComposerSurface')!.getBoundingClientRect().height))).toBe(true);
await expect.poll(floatingBottom).toBe(anchoredBottom);
const compactHeight = await workhub.evaluate(() => innerHeight);
+ const screenLayout = async () => {
+ const origin = await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows().find((window) => window.getTitle() === 'WorkHub')!.getContentBounds());
+ return workhub.evaluate(({ x, y }) => Array.from(document.querySelectorAll('.maka-composer-editor, .maka-composer button, .workHubExpandButton')).map((element) => {
+ const rect = element.getBoundingClientRect();
+ return { x: x + rect.x, y: y + rect.y, width: rect.width, height: rect.height };
+ }), origin);
+ };
+ const compactControls = await screenLayout();
await model.click();
const wheel = workhub.getByRole('listbox');
await expect(wheel).toBeVisible();
@@ -200,6 +211,12 @@ test('WorkHub uses its coordination model and shared attachment composer', async
await expect(workhub.locator('.workHubHistory')).toBeHidden();
await expect.poll(() => workhub.evaluate(() => innerHeight)).toBeGreaterThan(compactHeight);
await expect.poll(floatingBottom).toBe(anchoredBottom);
+ await expect.poll(screenLayout).toEqual(compactControls);
+ const expectWheelInsideWindow = () => expect.poll(() => wheel.evaluate((element) => {
+ const rect = element.getBoundingClientRect();
+ return rect.top >= 0 && rect.left >= 0 && rect.bottom <= innerHeight && rect.right <= innerWidth;
+ })).toBe(true);
+ await expectWheelInsideWindow();
const expectSnappedSelection = async (index: number) => {
const choice = modelChoices[index]!;
const option = wheel.getByRole('option').nth(index);
@@ -217,16 +234,12 @@ test('WorkHub uses its coordination model and shared attachment composer', async
await wheel.hover();
await workhub.mouse.wheel(0, scrollDirection * 30);
await expectSnappedSelection(initialIndex + scrollDirection);
- await expect.poll(() => wheel.evaluate((element) => {
- const selected = document.getElementById(element.getAttribute('aria-activedescendant')!)!.getBoundingClientRect();
- const viewport = element.getBoundingClientRect();
- return Math.abs((selected.top + selected.bottom - viewport.top - viewport.bottom) / 2);
- })).toBeLessThanOrEqual(1);
await wheel.press('Escape');
await model.click();
await expect(wheel.getByRole('option')).toHaveCount(3);
await expect.poll(() => workhub.evaluate(() => innerHeight === Math.ceil(document.querySelector('.workHubComposerSurface')!.getBoundingClientRect().height))).toBe(true);
const dragInitialTop = await wheel.evaluate((element) => element.scrollTop);
+ const beforeDrag = await screenLayout();
const dragDistance = dragInitialTop > 0 ? 32 : -32;
const wheelBounds = (await wheel.boundingBox())!;
const dragX = wheelBounds.x + wheelBounds.width / 2;
@@ -236,6 +249,7 @@ test('WorkHub uses its coordination model and shared attachment composer', async
await workhub.mouse.move(dragX, dragY + dragDistance, { steps: 8 });
await expect.poll(() => wheel.evaluate((element) => element.scrollTop)).toBe(dragInitialTop - dragDistance);
await workhub.mouse.up();
+ await expect.poll(screenLayout).toEqual(beforeDrag);
await expect(wheel).toBeVisible();
await expect.poll(() => wheel.evaluate((element) => element.scrollTop)).toBe(Math.round((dragInitialTop - dragDistance) / 44) * 44);
const draggedIndex = Math.round((dragInitialTop - dragDistance) / 44);
@@ -250,15 +264,7 @@ test('WorkHub uses its coordination model and shared attachment composer', async
const chooseLast = await options.first().getAttribute('aria-selected') === 'true';
await wheel.press(chooseLast ? 'End' : 'Home');
const selectedChoice = await expectSnappedSelection(chooseLast ? modelChoices.length - 1 : 0);
- const appearance = await page.evaluate(async () => (await window.maka.settings.getClient()).appearance);
- for (const theme of ['light', 'dark'] as const) {
- await page.evaluate((theme) => window.maka.settings.updateClient({ appearance: { theme } }), theme);
- await expect.poll(() => workhub.evaluate(() => document.documentElement.classList.contains('dark'))).toBe(theme === 'dark');
- await expect(wheel).toBeFocused();
- await expect(wheel).toHaveCSS('outline-style', 'solid');
- await workhub.locator('.workHubComposerSurface').screenshot({ path: testInfo.outputPath(`model-wheel-${theme}.png`), animations: 'disabled' });
- }
- await page.evaluate((appearance) => window.maka.settings.updateClient({ appearance }), appearance);
+ await expectWheelInsideWindow();
await wheel.press('Enter');
await expect(wheel).toHaveCount(0);
await expect(model).toBeFocused();
diff --git a/apps/desktop/src/renderer/features/workhub/ui/workhub-root.tsx b/apps/desktop/src/renderer/features/workhub/ui/workhub-root.tsx
index 3f45fbf277..f0fab04b92 100644
--- a/apps/desktop/src/renderer/features/workhub/ui/workhub-root.tsx
+++ b/apps/desktop/src/renderer/features/workhub/ui/workhub-root.tsx
@@ -296,7 +296,7 @@ function WorkHubContents() {
{controller.activeQuestion && <UserQuestionPrompt key={controller.activeQuestion.requestId}
request={controller.activeQuestion} onRespond={controller.respondToUserQuestion}
onStop={controller.stop} stopPending={controller.stopPending} />}
- <div hidden={Boolean(controller.activeQuestion || controller.activeForm)}>
+ <div className="workHubComposerContent" hidden={Boolean(controller.activeQuestion || controller.activeForm)}>
<WorkHubComposer
pendingMessages={controller.transientMessages}
queuedMessages={controller.messageQueue.entries}
@@ -336,10 +336,10 @@ function WorkHubContents() {
</div>
}
/>
- </div>
{!progress && floating && !conversationExpanded && (
<IconButton className="workHubExpandButton" type="button" size="sm" variant="ghost" icon={<ChevronDown size={14} style={{ rotate: '180deg' }} />} label={t.expandConversation} aria-expanded={false} onClick={toggleConversation} />
)}
+ </div>
</div>
}
>
diff --git a/apps/desktop/src/renderer/styles/workhub.css b/apps/desktop/src/renderer/styles/workhub.css
index f1803f14b2..6d9d0d553a 100644
--- a/apps/desktop/src/renderer/styles/workhub.css
+++ b/apps/desktop/src/renderer/styles/workhub.css
@@ -155,6 +155,13 @@
.workHubLiveWelcome p { color: var(--muted-foreground); }
.workHubLiveError { padding: var(--space-3); }
.workHubComposerSurface { position: relative; width: 100%; }
+ .workHubComposerContent { position: relative; }
+ /* Top-layer content still needs room inside the native window. Measuring
+ this leading space grows the bottom-anchored window upward, without
+ changing the composer's layout or its controls' screen positions. */
+ .workHubLive[data-placement='floating'][data-conversation-expanded='false'] .workHubComposerSurface:has(.maka-model-wheel-anchor[data-open='true'])::before {
+ content: ''; display: block; height: 136px;
+ }
.workHubComposerActions { display: flex; align-items: center; gap: 2px; margin-left: auto; }
.workHubRevealMark { position: absolute; inset: 32px 0 calc(var(--workhub-composer-height, 96px) + 12px); display: grid; place-items: center; z-index: 3; color: var(--foreground); opacity: 0; pointer-events: none; }
.workHubRevealMark svg { filter: drop-shadow(0 2px 12px var(--background)); }
diff --git a/apps/desktop/stories/settings/settings-pages.stories.tsx b/apps/desktop/stories/settings/settings-pages.stories.tsx
index 64594b32d9..132bed64db 100644
--- a/apps/desktop/stories/settings/settings-pages.stories.tsx
+++ b/apps/desktop/stories/settings/settings-pages.stories.tsx
@@ -2089,7 +2089,7 @@ export const General: Story = {
decorators: [withSettingsBridge],
render: () => <SettingsStory section="general" />,
};
-// Real path: 设置 → 通用 → 默认模型. Focus stays on the inline magnetic wheel;
+// Real path: 设置 → 通用 → 默认模型. Focus stays on the floating magnetic wheel;
// the containing settings row must not add a second focus ring.
export const GeneralPickerOpenFocusRing: Story = {
decorators: [withSettingsBridge],
@@ -2097,6 +2097,12 @@ export const GeneralPickerOpenFocusRing: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const trigger = await canvas.findByRole('button', { name: '默认模型' });
+ trigger.scrollIntoView({ block: 'center' });
+ const rows = () => Array.from(canvasElement.querySelectorAll('.astryx-item')).map((element) => {
+ const { x, y, width, height } = element.getBoundingClientRect();
+ return { x, y, width, height };
+ });
+ const before = rows();
await userEvent.click(trigger);
await waitFor(() => {
const active = document.activeElement as HTMLElement | null;
@@ -2106,6 +2112,11 @@ export const GeneralPickerOpenFocusRing: Story = {
const row = active.closest<HTMLElement>('.astryx-item');
expect(row).not.toBeNull();
expect(row ? getComputedStyle(row).outlineStyle : null).toBe('none');
+ expect(rows()).toEqual(before);
+ await userEvent.keyboard('{Escape}');
+ await waitFor(() => expect(trigger).toHaveFocus());
+ expect(rows()).toEqual(before);
+ await userEvent.click(trigger);
},
};
diff --git a/apps/desktop/stories/workhub.stories.tsx b/apps/desktop/stories/workhub.stories.tsx
index 5ac9f73e47..d119c8263b 100644
--- a/apps/desktop/stories/workhub.stories.tsx
+++ b/apps/desktop/stories/workhub.stories.tsx
@@ -169,19 +169,49 @@ export const FullConversationAndWorkIdentity: Story = {
},
};
export const FullConversationNarrow: Story = { ...FullConversationAndWorkIdentity, parameters: { viewport: { defaultViewport: 'tablet' } } };
+// Real path: the docked WorkHub composer opens its model wheel before sending.
export const StandardComposer: Story = {
render: () => <Surface />,
play: async ({ canvasElement }) => {
Object.values(writes).forEach((spy) => spy.mockClear());
const canvas = within(canvasElement); const page = within(canvasElement.ownerDocument.body);
await waitFor(() => expect(canvas.getByRole('button', { name: /切换当前任务模型/ })).toBeEnabled());
- await userEvent.click(canvas.getByRole('button', { name: /切换当前任务模型/ }));
+ const trigger = canvas.getByRole('button', { name: /切换当前任务模型/ });
+ const layout = () => Array.from(canvasElement.querySelectorAll('.maka-composer-editor, .maka-composer button')).map((element) => {
+ const { x, y, width, height } = element.getBoundingClientRect();
+ return { x, y, width, height };
+ });
+ const before = layout();
+ await userEvent.click(trigger);
const wheel = canvas.getByRole('listbox', { name: /切换当前任务模型/ });
+ await waitFor(() => expect(wheel).toHaveFocus());
+ expect(layout()).toEqual(before);
await expect(within(wheel).getByRole('option', { name: /model-a/, selected: true })).toBeInTheDocument();
await userEvent.keyboard('{End}');
await waitFor(() => expect(writes.model).toHaveBeenCalledWith(sessionId, expect.objectContaining({ expectedRevision: 1, modelTarget: expect.objectContaining({ model: 'model-b' }) })));
await waitFor(() => expect(within(wheel).getByRole('option', { name: /model-b/, selected: true })).toBeInTheDocument());
+ expect(layout()).toEqual(before);
+ await waitFor(() => {
+ const selected = wheel.querySelector('[aria-selected="true"]')!.getBoundingClientRect();
+ const viewport = wheel.getBoundingClientRect();
+ expect(Math.abs((selected.top + selected.bottom - viewport.top - viewport.bottom) / 2)).toBeLessThanOrEqual(1);
+ });
+ const activeLabel = wheel.querySelector('[data-active="true"] .maka-model-wheel-label')!;
+ const otherLabel = wheel.querySelector('[data-active="false"] .maka-model-wheel-label')!;
+ await waitFor(() => expect(new DOMMatrix(getComputedStyle(activeLabel).transform).a).toBeGreaterThan(new DOMMatrix(getComputedStyle(otherLabel).transform).a));
+ expect(getComputedStyle(wheel).outlineStyle).toBe('solid');
await userEvent.keyboard('{Escape}');
+ await waitFor(() => expect(trigger).toHaveFocus());
+ expect(layout()).toEqual(before);
+ await userEvent.click(trigger);
+ await waitFor(() => expect(canvas.getByRole('listbox')).toHaveFocus());
+ await userEvent.click(trigger);
+ await waitFor(() => expect(canvas.queryByRole('listbox')).not.toBeInTheDocument());
+ await userEvent.click(trigger);
+ await waitFor(() => expect(canvas.getByRole('listbox')).toHaveFocus());
+ await userEvent.tab();
+ await waitFor(() => expect(canvas.queryByRole('listbox')).not.toBeInTheDocument());
+ expect(canvasElement.ownerDocument.activeElement).not.toBe(canvasElement.ownerDocument.body);
await userEvent.click(canvas.getByRole('button', { name: '添加上下文' }));
await userEvent.click(page.getByRole('menuitem', { name: /添加文件/ }));
const editor = canvasElement.querySelector('[contenteditable="true"]') as HTMLElement;
diff --git a/packages/ui/src/__tests__/model-wheel-picker.test.tsx b/packages/ui/src/__tests__/model-wheel-picker.test.tsx
index 3746bb21ad..800ba3df4e 100644
--- a/packages/ui/src/__tests__/model-wheel-picker.test.tsx
+++ b/packages/ui/src/__tests__/model-wheel-picker.test.tsx
@@ -33,6 +33,8 @@ test('the wheel applies settled selection once and restores the saved model on f
}).IS_REACT_ACT_ENVIRONMENT,
};
const { document, window } = parseHTML('<div id="root"></div>');
+ window.getComputedStyle = () => ({ direction: 'ltr', writingMode: 'horizontal-tb',
+ getPropertyValue: () => '' }) as unknown as CSSStyleDeclaration;
Object.assign(globalThis, { document, window, IS_REACT_ACT_ENVIRONMENT: true });
const root = createRoot(document.querySelector('#root')!);
const calls: string[] = [];
diff --git a/packages/ui/src/model-wheel-picker.tsx b/packages/ui/src/model-wheel-picker.tsx
index d36c31d4d5..3003633857 100644
--- a/packages/ui/src/model-wheel-picker.tsx
+++ b/packages/ui/src/model-wheel-picker.tsx
@@ -18,7 +18,7 @@
*/
import { type ReactNode, useId, useLayoutEffect, useRef, useState } from 'react';
-import { Button } from '@astryxdesign/core';
+import { Button, useLayer } from '@astryxdesign/core';
import { Check, ICON_SIZE } from './icons.js';
export interface ModelWheelOption {
@@ -29,7 +29,7 @@ export interface ModelWheelOption {
disabled?: boolean;
}
-/** Shared inline picker: the model at the settled snap position takes effect. */
+/** Shared overlay picker: the model at the settled snap position takes effect. */
export function ModelWheelPicker(props: {
options: readonly ModelWheelOption[];
value?: string;
@@ -46,25 +46,44 @@ export function ModelWheelPicker(props: {
}) {
const [internalOpen, setInternalOpen] = useState(false);
const trigger = useRef<HTMLButtonElement>(null);
+ const anchor = useRef<HTMLSpanElement>(null);
const open = props.open ?? internalOpen;
const setOpen = (next: boolean) => {
if (props.open === undefined) setInternalOpen(next);
props.onOpenChange?.(next);
};
- return open ? <ModelWheel {...props} onClose={(restoreFocus) => {
- setOpen(false);
- if (restoreFocus) requestAnimationFrame(() => trigger.current?.focus({ preventScroll: true }));
- }} /> : <Button ref={trigger} type="button" variant="ghost" size={props.size ?? 'sm'}
- label={props.label} icon={props.icon} tooltip={props.tooltip}
+ const layer = useLayer({ mode: 'context', lazyMount: true, lightDismiss: true,
+ onHide: () => setOpen(false) });
+ useLayoutEffect(() => {
+ if (open) layer.show();
+ else layer.hide();
+ }, [open, layer.show, layer.hide]);
+ useLayoutEffect(() => {
+ const element = anchor.current;
+ if (!open || !element) return;
+ // Saving another model can change the label while the wheel is open.
+ // Keep its anchor's footprint stable throughout that interaction.
+ element.style.width = `${element.getBoundingClientRect().width}px`;
+ return () => { element.style.removeProperty('width'); };
+ }, [open]);
+ return <span ref={anchor} className="maka-model-wheel-anchor" data-open={open}>
+ <Button ref={(element) => { trigger.current = element; layer.ref(element); }} type="button" variant="ghost" size={props.size ?? 'sm'}
+ label={props.label} icon={props.icon} tooltip={open ? undefined : props.tooltip}
isDisabled={props.disabled || props.options.length === 0}
className={props.triggerClassName} aria-label={props.ariaLabel}
- aria-haspopup="listbox" aria-expanded={false}
- onClick={() => setOpen(true)}
+ aria-haspopup="listbox" aria-expanded={open} aria-controls={open ? layer.id : undefined}
+ onMouseDown={(event) => { if (open) event.preventDefault(); }}
+ onClick={() => setOpen(!open)}
onKeyDown={(event) => {
if (!props.disabled && props.options.length > 0 && (event.key === 'ArrowDown' || event.key === 'ArrowUp')) {
event.preventDefault(); event.stopPropagation(); setOpen(true);
}
- }} />;
+ }} />
+ {layer.render(open && layer.isOpen ? <ModelWheel {...props} onClose={(restoreFocus) => {
+ setOpen(false);
+ if (restoreFocus) requestAnimationFrame(() => trigger.current?.focus({ preventScroll: true }));
+ }} /> : null, { placement: 'above', alignment: 'start', offset: 4, className: 'maka-model-wheel-popup' })}
+ </span>;
}
const ROW_HEIGHT = 44;
diff --git a/packages/ui/src/styles.css b/packages/ui/src/styles.css
index d88ab05de1..0b8886dd7f 100644
--- a/packages/ui/src/styles.css
+++ b/packages/ui/src/styles.css
@@ -19,6 +19,10 @@
@import 'katex/dist/katex.min.css';
+.maka-model-wheel-anchor { display: inline-flex; min-width: 0; max-width: 100%; }
+.maka-model-wheel-anchor > button { min-width: 0; max-width: 100%; }
+.maka-model-wheel-anchor[data-open='true'] > button { width: 100%; }
+.maka-model-wheel-popup { border-radius: var(--radius-container); background: var(--color-background-popover); box-shadow: var(--shadow-med); }
.maka-model-wheel { position: relative; width: min(264px, 55vw); max-width: 100%; }
.maka-model-wheel::before { content: ''; position: absolute; inset: 44px 0; border-radius: 10px; background: color-mix(in oklch, var(--foreground) 6%, transparent); pointer-events: none; }
.maka-model-wheel-viewport { position: relative; box-sizing: border-box; overflow-y: auto; overscroll-behavior: contain; scroll-snap-type: y mandatory; scrollbar-width: none; mask-image: linear-gradient(transparent, black 32%, black 68%, transparent); outline: none; }
diff --git a/packages/ui/stories/model-picker.stories.tsx b/packages/ui/stories/model-picker.stories.tsx
index b1b62e5e73..e801c397b0 100644
--- a/packages/ui/stories/model-picker.stories.tsx
+++ b/packages/ui/stories/model-picker.stories.tsx
@@ -234,7 +234,7 @@ export const ExistingConversation: Story = {
await waitFor(() => expect(announcement).toBeEmptyDOMElement());
await expect(document.body.querySelector('.maka-model-switch-notice')).not.toBeInTheDocument();
- // Closing replaces the wheel with a new trigger and restores focus next frame.
+ // Closing restores focus to the same trigger next frame.
await waitFor(() => expect(within(canvasElement).getByRole('button', {
name: /切换当前任务模型|Switch model for this task/,
})).toHaveFocus());
diff --git a/scripts/storybook-visual-smoke.mjs b/scripts/storybook-visual-smoke.mjs
index e89465bd9f..f5388fcf34 100644
--- a/scripts/storybook-visual-smoke.mjs
+++ b/scripts/storybook-visual-smoke.mjs
@@ -71,6 +71,7 @@ const DARK_THEME_SENTINEL_STORY_IDS = new Set([
'product-settings-pages--appearance',
'product-settings-pages--bot-chat-needs-attention',
'product-shell-official-appshell--default-layout',
+ 'product-workhub--standard-composer',
]);
const FORCED_COLORS_STORY_IDS = new Set([
'product-settings-pages--general-forced-colors-focus-ring',
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org